From 4eceb95409e844fdc33c9c706e1dc307bfd40303 Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Wed, 19 Mar 2014 16:25:37 +0100 Subject: Initial Jack import. Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43 --- guava/src/com/google/common/annotations/Beta.java | 50 + .../google/common/annotations/GwtCompatible.java | 88 + .../google/common/annotations/GwtIncompatible.java | 52 + .../common/annotations/VisibleForTesting.java | 27 + .../google/common/annotations/package-info.java | 21 + guava/src/com/google/common/base/Absent.java | 87 + .../com/google/common/base/AbstractIterator.java | 84 + guava/src/com/google/common/base/Ascii.java | 487 ++ guava/src/com/google/common/base/CaseFormat.java | 164 + guava/src/com/google/common/base/CharMatcher.java | 1271 +++++ guava/src/com/google/common/base/Charsets.java | 83 + guava/src/com/google/common/base/Defaults.java | 60 + guava/src/com/google/common/base/Enums.java | 128 + guava/src/com/google/common/base/Equivalence.java | 358 ++ guava/src/com/google/common/base/Equivalences.java | 65 + .../common/base/FinalizablePhantomReference.java | 44 + .../google/common/base/FinalizableReference.java | 33 + .../common/base/FinalizableReferenceQueue.java | 299 ++ .../common/base/FinalizableSoftReference.java | 42 + .../common/base/FinalizableWeakReference.java | 42 + guava/src/com/google/common/base/Function.java | 63 + .../google/common/base/FunctionalEquivalence.java | 77 + guava/src/com/google/common/base/Functions.java | 356 ++ guava/src/com/google/common/base/Joiner.java | 562 +++ .../com/google/common/base/MediumCharMatcher.java | 133 + guava/src/com/google/common/base/Objects.java | 440 ++ guava/src/com/google/common/base/Optional.java | 245 + .../google/common/base/PairwiseEquivalence.java | 80 + guava/src/com/google/common/base/Platform.java | 55 + .../src/com/google/common/base/Preconditions.java | 449 ++ guava/src/com/google/common/base/Predicate.java | 63 + guava/src/com/google/common/base/Predicates.java | 626 +++ guava/src/com/google/common/base/Present.java | 92 + .../com/google/common/base/SmallCharMatcher.java | 128 + guava/src/com/google/common/base/Splitter.java | 571 +++ guava/src/com/google/common/base/Stopwatch.java | 226 + guava/src/com/google/common/base/Strings.java | 238 + guava/src/com/google/common/base/Supplier.java | 38 + guava/src/com/google/common/base/Suppliers.java | 293 ++ guava/src/com/google/common/base/Throwables.java | 220 + guava/src/com/google/common/base/Ticker.java | 63 + .../com/google/common/base/internal/Finalizer.java | 205 + guava/src/com/google/common/base/package-info.java | 66 + .../src/com/google/common/cache/AbstractCache.java | 275 ++ .../google/common/cache/AbstractLoadingCache.java | 79 + guava/src/com/google/common/cache/Cache.java | 147 + .../src/com/google/common/cache/CacheBuilder.java | 888 ++++ .../com/google/common/cache/CacheBuilderSpec.java | 455 ++ guava/src/com/google/common/cache/CacheLoader.java | 194 + guava/src/com/google/common/cache/CacheStats.java | 269 ++ .../com/google/common/cache/ForwardingCache.java | 146 + .../common/cache/ForwardingLoadingCache.java | 91 + .../src/com/google/common/cache/LoadingCache.java | 163 + guava/src/com/google/common/cache/LocalCache.java | 4913 ++++++++++++++++++++ guava/src/com/google/common/cache/LongAdder.java | 207 + .../src/com/google/common/cache/RemovalCause.java | 97 + .../com/google/common/cache/RemovalListener.java | 46 + .../com/google/common/cache/RemovalListeners.java | 57 + .../google/common/cache/RemovalNotification.java | 100 + guava/src/com/google/common/cache/Striped64.java | 344 ++ guava/src/com/google/common/cache/Weigher.java | 35 + .../src/com/google/common/cache/package-info.java | 38 + .../com/google/common/collect/AbstractBiMap.java | 404 ++ .../collect/AbstractIndexedListIterator.java | 109 + .../google/common/collect/AbstractIterator.java | 173 + .../common/collect/AbstractLinkedIterator.java | 84 + .../common/collect/AbstractListMultimap.java | 120 + .../common/collect/AbstractMapBasedMultiset.java | 325 ++ .../google/common/collect/AbstractMapEntry.java | 67 + .../google/common/collect/AbstractMultimap.java | 1414 ++++++ .../google/common/collect/AbstractMultiset.java | 221 + .../common/collect/AbstractSequentialIterator.java | 80 + .../google/common/collect/AbstractSetMultimap.java | 132 + .../common/collect/AbstractSortedMultiset.java | 147 + .../common/collect/AbstractSortedSetMultimap.java | 124 + .../google/common/collect/AllEqualOrdering.java | 66 + .../google/common/collect/ArrayListMultimap.java | 168 + .../src/com/google/common/collect/ArrayTable.java | 825 ++++ .../collect/AsynchronousComputationException.java | 37 + guava/src/com/google/common/collect/BiMap.java | 110 + guava/src/com/google/common/collect/BoundType.java | 57 + .../google/common/collect/ByFunctionOrdering.java | 70 + .../google/common/collect/ClassToInstanceMap.java | 66 + .../com/google/common/collect/Collections2.java | 703 +++ .../google/common/collect/ComparatorOrdering.java | 87 + .../com/google/common/collect/ComparisonChain.java | 225 + .../google/common/collect/CompoundOrdering.java | 71 + .../common/collect/ComputationException.java | 36 + .../common/collect/ComputingConcurrentHashMap.java | 454 ++ .../common/collect/ConcurrentHashMultiset.java | 605 +++ .../src/com/google/common/collect/Constraint.java | 67 + .../src/com/google/common/collect/Constraints.java | 382 ++ .../com/google/common/collect/ContiguousSet.java | 172 + guava/src/com/google/common/collect/Count.java | 74 + guava/src/com/google/common/collect/Cut.java | 347 ++ .../collect/DescendingImmutableSortedMultiset.java | 97 + .../com/google/common/collect/DiscreteDomain.java | 117 + .../com/google/common/collect/DiscreteDomains.java | 162 + .../google/common/collect/EmptyContiguousSet.java | 136 + .../google/common/collect/EmptyImmutableBiMap.java | 45 + .../google/common/collect/EmptyImmutableList.java | 131 + .../common/collect/EmptyImmutableListMultimap.java | 40 + .../google/common/collect/EmptyImmutableMap.java | 99 + .../common/collect/EmptyImmutableMultiset.java | 114 + .../google/common/collect/EmptyImmutableSet.java | 100 + .../common/collect/EmptyImmutableSetMultimap.java | 40 + .../common/collect/EmptyImmutableSortedMap.java | 119 + .../collect/EmptyImmutableSortedMultiset.java | 139 + .../common/collect/EmptyImmutableSortedSet.java | 127 + .../google/common/collect/EmptyImmutableTable.java | 128 + guava/src/com/google/common/collect/EnumBiMap.java | 151 + .../com/google/common/collect/EnumHashBiMap.java | 130 + .../com/google/common/collect/EnumMultiset.java | 94 + .../google/common/collect/ExplicitOrdering.java | 78 + .../com/google/common/collect/FluentIterable.java | 399 ++ .../common/collect/ForwardingCollection.java | 265 ++ .../common/collect/ForwardingConcurrentMap.java | 61 + .../com/google/common/collect/ForwardingDeque.java | 136 + .../common/collect/ForwardingImmutableList.java | 29 + .../common/collect/ForwardingImmutableMap.java | 29 + .../common/collect/ForwardingImmutableSet.java | 29 + .../google/common/collect/ForwardingIterator.java | 55 + .../com/google/common/collect/ForwardingList.java | 239 + .../common/collect/ForwardingListIterator.java | 70 + .../common/collect/ForwardingListMultimap.java | 54 + .../com/google/common/collect/ForwardingMap.java | 319 ++ .../google/common/collect/ForwardingMapEntry.java | 128 + .../google/common/collect/ForwardingMultimap.java | 143 + .../google/common/collect/ForwardingMultiset.java | 313 ++ .../common/collect/ForwardingNavigableMap.java | 412 ++ .../common/collect/ForwardingNavigableSet.java | 250 + .../google/common/collect/ForwardingObject.java | 76 + .../com/google/common/collect/ForwardingQueue.java | 123 + .../com/google/common/collect/ForwardingSet.java | 101 + .../common/collect/ForwardingSetMultimap.java | 56 + .../google/common/collect/ForwardingSortedMap.java | 171 + .../google/common/collect/ForwardingSortedSet.java | 166 + .../collect/ForwardingSortedSetMultimap.java | 60 + .../com/google/common/collect/ForwardingTable.java | 144 + .../com/google/common/collect/GeneralRange.java | 304 ++ .../com/google/common/collect/GenericMapMaker.java | 143 + .../com/google/common/collect/GwtTransient.java | 38 + .../com/google/common/collect/HashBasedTable.java | 148 + guava/src/com/google/common/collect/HashBiMap.java | 118 + .../com/google/common/collect/HashMultimap.java | 142 + .../com/google/common/collect/HashMultiset.java | 101 + guava/src/com/google/common/collect/Hashing.java | 43 + .../com/google/common/collect/ImmutableAsList.java | 84 + .../com/google/common/collect/ImmutableBiMap.java | 305 ++ .../collect/ImmutableClassToInstanceMap.java | 154 + .../google/common/collect/ImmutableCollection.java | 376 ++ .../com/google/common/collect/ImmutableEntry.java | 52 + .../google/common/collect/ImmutableEnumSet.java | 116 + .../com/google/common/collect/ImmutableList.java | 749 +++ .../common/collect/ImmutableListMultimap.java | 388 ++ .../com/google/common/collect/ImmutableMap.java | 462 ++ .../common/collect/ImmutableMapEntrySet.java | 76 + .../google/common/collect/ImmutableMapKeySet.java | 92 + .../google/common/collect/ImmutableMapValues.java | 89 + .../google/common/collect/ImmutableMultimap.java | 682 +++ .../google/common/collect/ImmutableMultiset.java | 596 +++ .../com/google/common/collect/ImmutableSet.java | 626 +++ .../common/collect/ImmutableSetMultimap.java | 510 ++ .../common/collect/ImmutableSortedAsList.java | 86 + .../google/common/collect/ImmutableSortedMap.java | 705 +++ .../collect/ImmutableSortedMapFauxverideShim.java | 117 + .../common/collect/ImmutableSortedMultiset.java | 574 +++ .../ImmutableSortedMultisetFauxverideShim.java | 170 + .../google/common/collect/ImmutableSortedSet.java | 845 ++++ .../collect/ImmutableSortedSetFauxverideShim.java | 167 + .../com/google/common/collect/ImmutableTable.java | 336 ++ guava/src/com/google/common/collect/Interner.java | 44 + guava/src/com/google/common/collect/Interners.java | 136 + guava/src/com/google/common/collect/Iterables.java | 1071 +++++ guava/src/com/google/common/collect/Iterators.java | 1427 ++++++ .../common/collect/LexicographicalOrdering.java | 78 + .../google/common/collect/LinkedHashMultimap.java | 615 +++ .../google/common/collect/LinkedHashMultiset.java | 113 + .../google/common/collect/LinkedListMultimap.java | 995 ++++ .../com/google/common/collect/ListMultimap.java | 96 + guava/src/com/google/common/collect/Lists.java | 1073 +++++ .../com/google/common/collect/MapConstraint.java | 65 + .../com/google/common/collect/MapConstraints.java | 783 ++++ .../com/google/common/collect/MapDifference.java | 114 + guava/src/com/google/common/collect/MapMaker.java | 887 ++++ .../google/common/collect/MapMakerInternalMap.java | 4081 ++++++++++++++++ guava/src/com/google/common/collect/Maps.java | 3240 +++++++++++++ .../google/common/collect/MinMaxPriorityQueue.java | 939 ++++ guava/src/com/google/common/collect/Multimap.java | 360 ++ guava/src/com/google/common/collect/Multimaps.java | 2695 +++++++++++ guava/src/com/google/common/collect/Multiset.java | 440 ++ guava/src/com/google/common/collect/Multisets.java | 972 ++++ .../common/collect/MutableClassToInstanceMap.java | 86 + .../com/google/common/collect/NaturalOrdering.java | 74 + .../google/common/collect/NullsFirstOrdering.java | 81 + .../google/common/collect/NullsLastOrdering.java | 81 + .../com/google/common/collect/ObjectArrays.java | 195 + guava/src/com/google/common/collect/Ordering.java | 791 ++++ .../com/google/common/collect/PeekingIterator.java | 69 + guava/src/com/google/common/collect/Platform.java | 77 + guava/src/com/google/common/collect/Queues.java | 341 ++ guava/src/com/google/common/collect/Range.java | 438 ++ guava/src/com/google/common/collect/RangeMap.java | 223 + guava/src/com/google/common/collect/RangeSet.java | 289 ++ guava/src/com/google/common/collect/Ranges.java | 255 + .../common/collect/RegularContiguousSet.java | 276 ++ .../common/collect/RegularImmutableAsList.java | 91 + .../common/collect/RegularImmutableBiMap.java | 60 + .../common/collect/RegularImmutableList.java | 139 + .../google/common/collect/RegularImmutableMap.java | 246 + .../common/collect/RegularImmutableMultiset.java | 110 + .../google/common/collect/RegularImmutableSet.java | 67 + .../common/collect/RegularImmutableSortedMap.java | 128 + .../collect/RegularImmutableSortedMultiset.java | 145 + .../common/collect/RegularImmutableSortedSet.java | 262 ++ .../common/collect/RegularImmutableTable.java | 559 +++ .../common/collect/ReverseNaturalOrdering.java | 92 + .../com/google/common/collect/ReverseOrdering.java | 100 + .../com/google/common/collect/RowSortedTable.java | 55 + .../com/google/common/collect/Serialization.java | 231 + .../src/com/google/common/collect/SetMultimap.java | 113 + guava/src/com/google/common/collect/Sets.java | 1666 +++++++ .../common/collect/SingletonImmutableList.java | 128 + .../common/collect/SingletonImmutableMap.java | 105 + .../common/collect/SingletonImmutableSet.java | 125 + .../common/collect/SingletonImmutableTable.java | 149 + .../com/google/common/collect/SortedIterable.java | 42 + .../com/google/common/collect/SortedIterables.java | 60 + .../src/com/google/common/collect/SortedLists.java | 284 ++ .../google/common/collect/SortedMapDifference.java | 45 + .../com/google/common/collect/SortedMultiset.java | 137 + .../com/google/common/collect/SortedMultisets.java | 196 + .../google/common/collect/SortedSetMultimap.java | 114 + .../common/collect/StandardRowSortedTable.java | 172 + .../com/google/common/collect/StandardTable.java | 1122 +++++ .../com/google/common/collect/Synchronized.java | 1565 +++++++ guava/src/com/google/common/collect/Table.java | 296 ++ guava/src/com/google/common/collect/Tables.java | 753 +++ .../common/collect/TransformedImmutableSet.java | 91 + .../google/common/collect/TransformedIterator.java | 55 + .../common/collect/TransformedListIterator.java | 71 + .../com/google/common/collect/TreeBasedTable.java | 380 ++ .../com/google/common/collect/TreeMultimap.java | 201 + .../com/google/common/collect/TreeMultiset.java | 982 ++++ .../com/google/common/collect/TreeRangeSet.java | 203 + .../common/collect/UnmodifiableIterator.java | 43 + .../common/collect/UnmodifiableListIterator.java | 53 + .../common/collect/UsingToStringOrdering.java | 45 + .../com/google/common/collect/WellBehavedMap.java | 98 + .../com/google/common/collect/package-info.java | 226 + .../common/eventbus/AllowConcurrentEvents.java | 41 + .../common/eventbus/AnnotatedHandlerFinder.java | 106 + .../com/google/common/eventbus/AsyncEventBus.java | 101 + .../src/com/google/common/eventbus/DeadEvent.java | 69 + guava/src/com/google/common/eventbus/EventBus.java | 358 ++ .../com/google/common/eventbus/EventHandler.java | 109 + .../common/eventbus/HandlerFindingStrategy.java | 42 + .../src/com/google/common/eventbus/Subscribe.java | 46 + .../common/eventbus/SynchronizedEventHandler.java | 48 + .../com/google/common/eventbus/package-info.java | 254 + .../common/hash/AbstractCompositeHashFunction.java | 148 + .../src/com/google/common/hash/AbstractHasher.java | 49 + .../hash/AbstractNonStreamingHashFunction.java | 158 + .../common/hash/AbstractStreamingHashFunction.java | 253 + guava/src/com/google/common/hash/BloomFilter.java | 310 ++ .../google/common/hash/BloomFilterStrategies.java | 137 + guava/src/com/google/common/hash/Funnel.java | 38 + guava/src/com/google/common/hash/Funnels.java | 148 + guava/src/com/google/common/hash/HashCode.java | 120 + guava/src/com/google/common/hash/HashCodes.java | 171 + guava/src/com/google/common/hash/HashFunction.java | 207 + guava/src/com/google/common/hash/Hasher.java | 75 + guava/src/com/google/common/hash/Hashing.java | 336 ++ .../common/hash/MessageDigestHashFunction.java | 169 + .../common/hash/Murmur3_128HashFunction.java | 172 + .../google/common/hash/Murmur3_32HashFunction.java | 158 + .../src/com/google/common/hash/PrimitiveSink.java | 102 + guava/src/com/google/common/hash/package-info.java | 26 + .../src/com/google/common/io/AppendableWriter.java | 117 + .../com/google/common/io/ByteArrayDataInput.java | 65 + .../com/google/common/io/ByteArrayDataOutput.java | 55 + guava/src/com/google/common/io/ByteProcessor.java | 49 + guava/src/com/google/common/io/ByteStreams.java | 871 ++++ guava/src/com/google/common/io/CharStreams.java | 441 ++ guava/src/com/google/common/io/Closeables.java | 104 + .../com/google/common/io/CountingInputStream.java | 90 + .../com/google/common/io/CountingOutputStream.java | 59 + .../google/common/io/FileBackedOutputStream.java | 210 + guava/src/com/google/common/io/Files.java | 780 ++++ guava/src/com/google/common/io/Flushables.java | 80 + guava/src/com/google/common/io/InputSupplier.java | 40 + .../src/com/google/common/io/LimitInputStream.java | 104 + guava/src/com/google/common/io/LineBuffer.java | 117 + guava/src/com/google/common/io/LineProcessor.java | 45 + guava/src/com/google/common/io/LineReader.java | 87 + .../common/io/LittleEndianDataInputStream.java | 232 + .../common/io/LittleEndianDataOutputStream.java | 164 + .../src/com/google/common/io/MultiInputStream.java | 115 + guava/src/com/google/common/io/MultiReader.java | 90 + .../src/com/google/common/io/NullOutputStream.java | 38 + guava/src/com/google/common/io/OutputSupplier.java | 40 + .../google/common/io/PatternFilenameFilter.java | 62 + guava/src/com/google/common/io/Resources.java | 173 + guava/src/com/google/common/io/package-info.java | 41 + .../src/com/google/common/math/BigIntegerMath.java | 451 ++ guava/src/com/google/common/math/DoubleMath.java | 379 ++ guava/src/com/google/common/math/DoubleUtils.java | 148 + guava/src/com/google/common/math/IntMath.java | 551 +++ guava/src/com/google/common/math/LongMath.java | 675 +++ .../com/google/common/math/MathPreconditions.java | 98 + guava/src/com/google/common/math/package-info.java | 31 + guava/src/com/google/common/net/HostAndPort.java | 268 ++ guava/src/com/google/common/net/HostSpecifier.java | 178 + guava/src/com/google/common/net/HttpHeaders.java | 200 + guava/src/com/google/common/net/InetAddresses.java | 1011 ++++ .../com/google/common/net/InternetDomainName.java | 580 +++ guava/src/com/google/common/net/MediaType.java | 667 +++ guava/src/com/google/common/net/TldPatterns.java | 4633 ++++++++++++++++++ guava/src/com/google/common/net/package-info.java | 30 + .../google/common/primitives/AndroidInteger.java | 91 + .../src/com/google/common/primitives/Booleans.java | 471 ++ guava/src/com/google/common/primitives/Bytes.java | 389 ++ guava/src/com/google/common/primitives/Chars.java | 587 +++ .../src/com/google/common/primitives/Doubles.java | 534 +++ guava/src/com/google/common/primitives/Floats.java | 531 +++ guava/src/com/google/common/primitives/Ints.java | 620 +++ guava/src/com/google/common/primitives/Longs.java | 575 +++ .../com/google/common/primitives/ParseRequest.java | 57 + .../com/google/common/primitives/Primitives.java | 134 + guava/src/com/google/common/primitives/Shorts.java | 593 +++ .../com/google/common/primitives/SignedBytes.java | 194 + .../google/common/primitives/UnsignedBytes.java | 438 ++ .../google/common/primitives/UnsignedInteger.java | 240 + .../com/google/common/primitives/UnsignedInts.java | 275 ++ .../com/google/common/primitives/UnsignedLong.java | 243 + .../google/common/primitives/UnsignedLongs.java | 391 ++ .../com/google/common/primitives/package-info.java | 69 + .../common/reflect/AbstractInvocationHandler.java | 82 + .../common/reflect/ImmutableTypeToInstanceMap.java | 138 + .../common/reflect/MutableTypeToInstanceMap.java | 89 + .../src/com/google/common/reflect/Reflection.java | 103 + .../src/com/google/common/reflect/TypeCapture.java | 38 + .../com/google/common/reflect/TypeParameter.java | 70 + .../com/google/common/reflect/TypeResolver.java | 389 ++ .../google/common/reflect/TypeToInstanceMap.java | 92 + guava/src/com/google/common/reflect/TypeToken.java | 1086 +++++ guava/src/com/google/common/reflect/Types.java | 518 +++ .../com/google/common/reflect/package-info.java | 23 + .../util/concurrent/AbstractCheckedFuture.java | 117 + .../concurrent/AbstractExecutionThreadService.java | 188 + .../common/util/concurrent/AbstractFuture.java | 371 ++ .../util/concurrent/AbstractIdleService.java | 132 + .../AbstractListeningExecutorService.java | 163 + .../util/concurrent/AbstractScheduledService.java | 446 ++ .../common/util/concurrent/AbstractService.java | 536 +++ .../common/util/concurrent/AsyncFunction.java | 41 + .../common/util/concurrent/AtomicDouble.java | 257 + .../common/util/concurrent/AtomicDoubleArray.java | 271 ++ .../common/util/concurrent/AtomicLongMap.java | 434 ++ .../com/google/common/util/concurrent/Atomics.java | 76 + .../google/common/util/concurrent/Callables.java | 43 + .../common/util/concurrent/CheckedFuture.java | 76 + .../util/concurrent/CycleDetectingLockFactory.java | 1034 ++++ .../common/util/concurrent/ExecutionError.java | 64 + .../common/util/concurrent/ExecutionList.java | 159 + .../common/util/concurrent/FakeTimeLimiter.java | 47 + .../util/concurrent/ForwardingBlockingQueue.java | 74 + .../util/concurrent/ForwardingCheckedFuture.java | 78 + .../util/concurrent/ForwardingExecutorService.java | 117 + .../common/util/concurrent/ForwardingFuture.java | 96 + .../concurrent/ForwardingListenableFuture.java | 74 + .../ForwardingListeningExecutorService.java | 52 + .../common/util/concurrent/ForwardingService.java | 86 + .../common/util/concurrent/FutureCallback.java | 49 + .../com/google/common/util/concurrent/Futures.java | 1249 +++++ .../common/util/concurrent/JdkFutureAdapters.java | 182 + .../common/util/concurrent/ListenableFuture.java | 135 + .../util/concurrent/ListenableFutureTask.java | 90 + .../util/concurrent/ListeningExecutorService.java | 88 + .../ListeningScheduledExecutorService.java | 40 + .../com/google/common/util/concurrent/Monitor.java | 942 ++++ .../common/util/concurrent/MoreExecutors.java | 587 +++ .../google/common/util/concurrent/RateLimiter.java | 658 +++ .../com/google/common/util/concurrent/Service.java | 234 + .../common/util/concurrent/SettableFuture.java | 70 + .../common/util/concurrent/SimpleTimeLimiter.java | 195 + .../com/google/common/util/concurrent/Striped.java | 376 ++ .../util/concurrent/ThreadFactoryBuilder.java | 176 + .../google/common/util/concurrent/TimeLimiter.java | 106 + .../util/concurrent/UncaughtExceptionHandlers.java | 65 + .../concurrent/UncheckedExecutionException.java | 69 + .../util/concurrent/UncheckedTimeoutException.java | 41 + .../common/util/concurrent/Uninterruptibles.java | 278 ++ .../common/util/concurrent/package-info.java | 36 + 394 files changed, 113792 insertions(+) create mode 100644 guava/src/com/google/common/annotations/Beta.java create mode 100644 guava/src/com/google/common/annotations/GwtCompatible.java create mode 100644 guava/src/com/google/common/annotations/GwtIncompatible.java create mode 100644 guava/src/com/google/common/annotations/VisibleForTesting.java create mode 100644 guava/src/com/google/common/annotations/package-info.java create mode 100644 guava/src/com/google/common/base/Absent.java create mode 100644 guava/src/com/google/common/base/AbstractIterator.java create mode 100644 guava/src/com/google/common/base/Ascii.java create mode 100644 guava/src/com/google/common/base/CaseFormat.java create mode 100644 guava/src/com/google/common/base/CharMatcher.java create mode 100644 guava/src/com/google/common/base/Charsets.java create mode 100644 guava/src/com/google/common/base/Defaults.java create mode 100644 guava/src/com/google/common/base/Enums.java create mode 100644 guava/src/com/google/common/base/Equivalence.java create mode 100644 guava/src/com/google/common/base/Equivalences.java create mode 100644 guava/src/com/google/common/base/FinalizablePhantomReference.java create mode 100644 guava/src/com/google/common/base/FinalizableReference.java create mode 100644 guava/src/com/google/common/base/FinalizableReferenceQueue.java create mode 100644 guava/src/com/google/common/base/FinalizableSoftReference.java create mode 100644 guava/src/com/google/common/base/FinalizableWeakReference.java create mode 100644 guava/src/com/google/common/base/Function.java create mode 100644 guava/src/com/google/common/base/FunctionalEquivalence.java create mode 100644 guava/src/com/google/common/base/Functions.java create mode 100644 guava/src/com/google/common/base/Joiner.java create mode 100644 guava/src/com/google/common/base/MediumCharMatcher.java create mode 100644 guava/src/com/google/common/base/Objects.java create mode 100644 guava/src/com/google/common/base/Optional.java create mode 100644 guava/src/com/google/common/base/PairwiseEquivalence.java create mode 100644 guava/src/com/google/common/base/Platform.java create mode 100644 guava/src/com/google/common/base/Preconditions.java create mode 100644 guava/src/com/google/common/base/Predicate.java create mode 100644 guava/src/com/google/common/base/Predicates.java create mode 100644 guava/src/com/google/common/base/Present.java create mode 100644 guava/src/com/google/common/base/SmallCharMatcher.java create mode 100644 guava/src/com/google/common/base/Splitter.java create mode 100644 guava/src/com/google/common/base/Stopwatch.java create mode 100644 guava/src/com/google/common/base/Strings.java create mode 100644 guava/src/com/google/common/base/Supplier.java create mode 100644 guava/src/com/google/common/base/Suppliers.java create mode 100644 guava/src/com/google/common/base/Throwables.java create mode 100644 guava/src/com/google/common/base/Ticker.java create mode 100644 guava/src/com/google/common/base/internal/Finalizer.java create mode 100644 guava/src/com/google/common/base/package-info.java create mode 100644 guava/src/com/google/common/cache/AbstractCache.java create mode 100644 guava/src/com/google/common/cache/AbstractLoadingCache.java create mode 100644 guava/src/com/google/common/cache/Cache.java create mode 100644 guava/src/com/google/common/cache/CacheBuilder.java create mode 100644 guava/src/com/google/common/cache/CacheBuilderSpec.java create mode 100644 guava/src/com/google/common/cache/CacheLoader.java create mode 100644 guava/src/com/google/common/cache/CacheStats.java create mode 100644 guava/src/com/google/common/cache/ForwardingCache.java create mode 100644 guava/src/com/google/common/cache/ForwardingLoadingCache.java create mode 100644 guava/src/com/google/common/cache/LoadingCache.java create mode 100644 guava/src/com/google/common/cache/LocalCache.java create mode 100644 guava/src/com/google/common/cache/LongAdder.java create mode 100644 guava/src/com/google/common/cache/RemovalCause.java create mode 100644 guava/src/com/google/common/cache/RemovalListener.java create mode 100644 guava/src/com/google/common/cache/RemovalListeners.java create mode 100644 guava/src/com/google/common/cache/RemovalNotification.java create mode 100644 guava/src/com/google/common/cache/Striped64.java create mode 100644 guava/src/com/google/common/cache/Weigher.java create mode 100644 guava/src/com/google/common/cache/package-info.java create mode 100644 guava/src/com/google/common/collect/AbstractBiMap.java create mode 100644 guava/src/com/google/common/collect/AbstractIndexedListIterator.java create mode 100644 guava/src/com/google/common/collect/AbstractIterator.java create mode 100644 guava/src/com/google/common/collect/AbstractLinkedIterator.java create mode 100644 guava/src/com/google/common/collect/AbstractListMultimap.java create mode 100644 guava/src/com/google/common/collect/AbstractMapBasedMultiset.java create mode 100644 guava/src/com/google/common/collect/AbstractMapEntry.java create mode 100644 guava/src/com/google/common/collect/AbstractMultimap.java create mode 100644 guava/src/com/google/common/collect/AbstractMultiset.java create mode 100644 guava/src/com/google/common/collect/AbstractSequentialIterator.java create mode 100644 guava/src/com/google/common/collect/AbstractSetMultimap.java create mode 100644 guava/src/com/google/common/collect/AbstractSortedMultiset.java create mode 100644 guava/src/com/google/common/collect/AbstractSortedSetMultimap.java create mode 100644 guava/src/com/google/common/collect/AllEqualOrdering.java create mode 100644 guava/src/com/google/common/collect/ArrayListMultimap.java create mode 100644 guava/src/com/google/common/collect/ArrayTable.java create mode 100644 guava/src/com/google/common/collect/AsynchronousComputationException.java create mode 100644 guava/src/com/google/common/collect/BiMap.java create mode 100644 guava/src/com/google/common/collect/BoundType.java create mode 100644 guava/src/com/google/common/collect/ByFunctionOrdering.java create mode 100644 guava/src/com/google/common/collect/ClassToInstanceMap.java create mode 100644 guava/src/com/google/common/collect/Collections2.java create mode 100644 guava/src/com/google/common/collect/ComparatorOrdering.java create mode 100644 guava/src/com/google/common/collect/ComparisonChain.java create mode 100644 guava/src/com/google/common/collect/CompoundOrdering.java create mode 100644 guava/src/com/google/common/collect/ComputationException.java create mode 100644 guava/src/com/google/common/collect/ComputingConcurrentHashMap.java create mode 100644 guava/src/com/google/common/collect/ConcurrentHashMultiset.java create mode 100644 guava/src/com/google/common/collect/Constraint.java create mode 100644 guava/src/com/google/common/collect/Constraints.java create mode 100644 guava/src/com/google/common/collect/ContiguousSet.java create mode 100644 guava/src/com/google/common/collect/Count.java create mode 100644 guava/src/com/google/common/collect/Cut.java create mode 100644 guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java create mode 100644 guava/src/com/google/common/collect/DiscreteDomain.java create mode 100644 guava/src/com/google/common/collect/DiscreteDomains.java create mode 100644 guava/src/com/google/common/collect/EmptyContiguousSet.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableBiMap.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableList.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableListMultimap.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableMap.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableMultiset.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableSet.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableSortedMap.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableSortedMultiset.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableSortedSet.java create mode 100644 guava/src/com/google/common/collect/EmptyImmutableTable.java create mode 100644 guava/src/com/google/common/collect/EnumBiMap.java create mode 100644 guava/src/com/google/common/collect/EnumHashBiMap.java create mode 100644 guava/src/com/google/common/collect/EnumMultiset.java create mode 100644 guava/src/com/google/common/collect/ExplicitOrdering.java create mode 100644 guava/src/com/google/common/collect/FluentIterable.java create mode 100644 guava/src/com/google/common/collect/ForwardingCollection.java create mode 100644 guava/src/com/google/common/collect/ForwardingConcurrentMap.java create mode 100644 guava/src/com/google/common/collect/ForwardingDeque.java create mode 100644 guava/src/com/google/common/collect/ForwardingImmutableList.java create mode 100644 guava/src/com/google/common/collect/ForwardingImmutableMap.java create mode 100644 guava/src/com/google/common/collect/ForwardingImmutableSet.java create mode 100644 guava/src/com/google/common/collect/ForwardingIterator.java create mode 100644 guava/src/com/google/common/collect/ForwardingList.java create mode 100644 guava/src/com/google/common/collect/ForwardingListIterator.java create mode 100644 guava/src/com/google/common/collect/ForwardingListMultimap.java create mode 100644 guava/src/com/google/common/collect/ForwardingMap.java create mode 100644 guava/src/com/google/common/collect/ForwardingMapEntry.java create mode 100644 guava/src/com/google/common/collect/ForwardingMultimap.java create mode 100644 guava/src/com/google/common/collect/ForwardingMultiset.java create mode 100644 guava/src/com/google/common/collect/ForwardingNavigableMap.java create mode 100644 guava/src/com/google/common/collect/ForwardingNavigableSet.java create mode 100644 guava/src/com/google/common/collect/ForwardingObject.java create mode 100644 guava/src/com/google/common/collect/ForwardingQueue.java create mode 100644 guava/src/com/google/common/collect/ForwardingSet.java create mode 100644 guava/src/com/google/common/collect/ForwardingSetMultimap.java create mode 100644 guava/src/com/google/common/collect/ForwardingSortedMap.java create mode 100644 guava/src/com/google/common/collect/ForwardingSortedSet.java create mode 100644 guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java create mode 100644 guava/src/com/google/common/collect/ForwardingTable.java create mode 100644 guava/src/com/google/common/collect/GeneralRange.java create mode 100644 guava/src/com/google/common/collect/GenericMapMaker.java create mode 100644 guava/src/com/google/common/collect/GwtTransient.java create mode 100644 guava/src/com/google/common/collect/HashBasedTable.java create mode 100644 guava/src/com/google/common/collect/HashBiMap.java create mode 100644 guava/src/com/google/common/collect/HashMultimap.java create mode 100644 guava/src/com/google/common/collect/HashMultiset.java create mode 100644 guava/src/com/google/common/collect/Hashing.java create mode 100644 guava/src/com/google/common/collect/ImmutableAsList.java create mode 100644 guava/src/com/google/common/collect/ImmutableBiMap.java create mode 100644 guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java create mode 100644 guava/src/com/google/common/collect/ImmutableCollection.java create mode 100644 guava/src/com/google/common/collect/ImmutableEntry.java create mode 100644 guava/src/com/google/common/collect/ImmutableEnumSet.java create mode 100644 guava/src/com/google/common/collect/ImmutableList.java create mode 100644 guava/src/com/google/common/collect/ImmutableListMultimap.java create mode 100644 guava/src/com/google/common/collect/ImmutableMap.java create mode 100644 guava/src/com/google/common/collect/ImmutableMapEntrySet.java create mode 100644 guava/src/com/google/common/collect/ImmutableMapKeySet.java create mode 100644 guava/src/com/google/common/collect/ImmutableMapValues.java create mode 100644 guava/src/com/google/common/collect/ImmutableMultimap.java create mode 100644 guava/src/com/google/common/collect/ImmutableMultiset.java create mode 100644 guava/src/com/google/common/collect/ImmutableSet.java create mode 100644 guava/src/com/google/common/collect/ImmutableSetMultimap.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedAsList.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedMap.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedMultiset.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedSet.java create mode 100644 guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java create mode 100644 guava/src/com/google/common/collect/ImmutableTable.java create mode 100644 guava/src/com/google/common/collect/Interner.java create mode 100644 guava/src/com/google/common/collect/Interners.java create mode 100644 guava/src/com/google/common/collect/Iterables.java create mode 100644 guava/src/com/google/common/collect/Iterators.java create mode 100644 guava/src/com/google/common/collect/LexicographicalOrdering.java create mode 100644 guava/src/com/google/common/collect/LinkedHashMultimap.java create mode 100644 guava/src/com/google/common/collect/LinkedHashMultiset.java create mode 100644 guava/src/com/google/common/collect/LinkedListMultimap.java create mode 100644 guava/src/com/google/common/collect/ListMultimap.java create mode 100644 guava/src/com/google/common/collect/Lists.java create mode 100644 guava/src/com/google/common/collect/MapConstraint.java create mode 100644 guava/src/com/google/common/collect/MapConstraints.java create mode 100644 guava/src/com/google/common/collect/MapDifference.java create mode 100644 guava/src/com/google/common/collect/MapMaker.java create mode 100644 guava/src/com/google/common/collect/MapMakerInternalMap.java create mode 100644 guava/src/com/google/common/collect/Maps.java create mode 100644 guava/src/com/google/common/collect/MinMaxPriorityQueue.java create mode 100644 guava/src/com/google/common/collect/Multimap.java create mode 100644 guava/src/com/google/common/collect/Multimaps.java create mode 100644 guava/src/com/google/common/collect/Multiset.java create mode 100644 guava/src/com/google/common/collect/Multisets.java create mode 100644 guava/src/com/google/common/collect/MutableClassToInstanceMap.java create mode 100644 guava/src/com/google/common/collect/NaturalOrdering.java create mode 100644 guava/src/com/google/common/collect/NullsFirstOrdering.java create mode 100644 guava/src/com/google/common/collect/NullsLastOrdering.java create mode 100644 guava/src/com/google/common/collect/ObjectArrays.java create mode 100644 guava/src/com/google/common/collect/Ordering.java create mode 100644 guava/src/com/google/common/collect/PeekingIterator.java create mode 100644 guava/src/com/google/common/collect/Platform.java create mode 100644 guava/src/com/google/common/collect/Queues.java create mode 100644 guava/src/com/google/common/collect/Range.java create mode 100644 guava/src/com/google/common/collect/RangeMap.java create mode 100644 guava/src/com/google/common/collect/RangeSet.java create mode 100644 guava/src/com/google/common/collect/Ranges.java create mode 100644 guava/src/com/google/common/collect/RegularContiguousSet.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableAsList.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableBiMap.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableList.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableMap.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableMultiset.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableSet.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableSortedMap.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableSortedSet.java create mode 100644 guava/src/com/google/common/collect/RegularImmutableTable.java create mode 100644 guava/src/com/google/common/collect/ReverseNaturalOrdering.java create mode 100644 guava/src/com/google/common/collect/ReverseOrdering.java create mode 100644 guava/src/com/google/common/collect/RowSortedTable.java create mode 100644 guava/src/com/google/common/collect/Serialization.java create mode 100644 guava/src/com/google/common/collect/SetMultimap.java create mode 100644 guava/src/com/google/common/collect/Sets.java create mode 100644 guava/src/com/google/common/collect/SingletonImmutableList.java create mode 100644 guava/src/com/google/common/collect/SingletonImmutableMap.java create mode 100644 guava/src/com/google/common/collect/SingletonImmutableSet.java create mode 100644 guava/src/com/google/common/collect/SingletonImmutableTable.java create mode 100644 guava/src/com/google/common/collect/SortedIterable.java create mode 100644 guava/src/com/google/common/collect/SortedIterables.java create mode 100644 guava/src/com/google/common/collect/SortedLists.java create mode 100644 guava/src/com/google/common/collect/SortedMapDifference.java create mode 100644 guava/src/com/google/common/collect/SortedMultiset.java create mode 100644 guava/src/com/google/common/collect/SortedMultisets.java create mode 100644 guava/src/com/google/common/collect/SortedSetMultimap.java create mode 100644 guava/src/com/google/common/collect/StandardRowSortedTable.java create mode 100644 guava/src/com/google/common/collect/StandardTable.java create mode 100644 guava/src/com/google/common/collect/Synchronized.java create mode 100644 guava/src/com/google/common/collect/Table.java create mode 100644 guava/src/com/google/common/collect/Tables.java create mode 100644 guava/src/com/google/common/collect/TransformedImmutableSet.java create mode 100644 guava/src/com/google/common/collect/TransformedIterator.java create mode 100644 guava/src/com/google/common/collect/TransformedListIterator.java create mode 100644 guava/src/com/google/common/collect/TreeBasedTable.java create mode 100644 guava/src/com/google/common/collect/TreeMultimap.java create mode 100644 guava/src/com/google/common/collect/TreeMultiset.java create mode 100644 guava/src/com/google/common/collect/TreeRangeSet.java create mode 100644 guava/src/com/google/common/collect/UnmodifiableIterator.java create mode 100644 guava/src/com/google/common/collect/UnmodifiableListIterator.java create mode 100644 guava/src/com/google/common/collect/UsingToStringOrdering.java create mode 100644 guava/src/com/google/common/collect/WellBehavedMap.java create mode 100644 guava/src/com/google/common/collect/package-info.java create mode 100644 guava/src/com/google/common/eventbus/AllowConcurrentEvents.java create mode 100644 guava/src/com/google/common/eventbus/AnnotatedHandlerFinder.java create mode 100644 guava/src/com/google/common/eventbus/AsyncEventBus.java create mode 100644 guava/src/com/google/common/eventbus/DeadEvent.java create mode 100644 guava/src/com/google/common/eventbus/EventBus.java create mode 100644 guava/src/com/google/common/eventbus/EventHandler.java create mode 100644 guava/src/com/google/common/eventbus/HandlerFindingStrategy.java create mode 100644 guava/src/com/google/common/eventbus/Subscribe.java create mode 100644 guava/src/com/google/common/eventbus/SynchronizedEventHandler.java create mode 100644 guava/src/com/google/common/eventbus/package-info.java create mode 100644 guava/src/com/google/common/hash/AbstractCompositeHashFunction.java create mode 100644 guava/src/com/google/common/hash/AbstractHasher.java create mode 100644 guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java create mode 100644 guava/src/com/google/common/hash/AbstractStreamingHashFunction.java create mode 100644 guava/src/com/google/common/hash/BloomFilter.java create mode 100644 guava/src/com/google/common/hash/BloomFilterStrategies.java create mode 100644 guava/src/com/google/common/hash/Funnel.java create mode 100644 guava/src/com/google/common/hash/Funnels.java create mode 100644 guava/src/com/google/common/hash/HashCode.java create mode 100644 guava/src/com/google/common/hash/HashCodes.java create mode 100644 guava/src/com/google/common/hash/HashFunction.java create mode 100644 guava/src/com/google/common/hash/Hasher.java create mode 100644 guava/src/com/google/common/hash/Hashing.java create mode 100644 guava/src/com/google/common/hash/MessageDigestHashFunction.java create mode 100644 guava/src/com/google/common/hash/Murmur3_128HashFunction.java create mode 100644 guava/src/com/google/common/hash/Murmur3_32HashFunction.java create mode 100644 guava/src/com/google/common/hash/PrimitiveSink.java create mode 100644 guava/src/com/google/common/hash/package-info.java create mode 100644 guava/src/com/google/common/io/AppendableWriter.java create mode 100644 guava/src/com/google/common/io/ByteArrayDataInput.java create mode 100644 guava/src/com/google/common/io/ByteArrayDataOutput.java create mode 100644 guava/src/com/google/common/io/ByteProcessor.java create mode 100644 guava/src/com/google/common/io/ByteStreams.java create mode 100644 guava/src/com/google/common/io/CharStreams.java create mode 100644 guava/src/com/google/common/io/Closeables.java create mode 100644 guava/src/com/google/common/io/CountingInputStream.java create mode 100644 guava/src/com/google/common/io/CountingOutputStream.java create mode 100644 guava/src/com/google/common/io/FileBackedOutputStream.java create mode 100644 guava/src/com/google/common/io/Files.java create mode 100644 guava/src/com/google/common/io/Flushables.java create mode 100644 guava/src/com/google/common/io/InputSupplier.java create mode 100644 guava/src/com/google/common/io/LimitInputStream.java create mode 100644 guava/src/com/google/common/io/LineBuffer.java create mode 100644 guava/src/com/google/common/io/LineProcessor.java create mode 100644 guava/src/com/google/common/io/LineReader.java create mode 100644 guava/src/com/google/common/io/LittleEndianDataInputStream.java create mode 100644 guava/src/com/google/common/io/LittleEndianDataOutputStream.java create mode 100644 guava/src/com/google/common/io/MultiInputStream.java create mode 100644 guava/src/com/google/common/io/MultiReader.java create mode 100644 guava/src/com/google/common/io/NullOutputStream.java create mode 100644 guava/src/com/google/common/io/OutputSupplier.java create mode 100644 guava/src/com/google/common/io/PatternFilenameFilter.java create mode 100644 guava/src/com/google/common/io/Resources.java create mode 100644 guava/src/com/google/common/io/package-info.java create mode 100644 guava/src/com/google/common/math/BigIntegerMath.java create mode 100644 guava/src/com/google/common/math/DoubleMath.java create mode 100644 guava/src/com/google/common/math/DoubleUtils.java create mode 100644 guava/src/com/google/common/math/IntMath.java create mode 100644 guava/src/com/google/common/math/LongMath.java create mode 100644 guava/src/com/google/common/math/MathPreconditions.java create mode 100644 guava/src/com/google/common/math/package-info.java create mode 100644 guava/src/com/google/common/net/HostAndPort.java create mode 100644 guava/src/com/google/common/net/HostSpecifier.java create mode 100644 guava/src/com/google/common/net/HttpHeaders.java create mode 100644 guava/src/com/google/common/net/InetAddresses.java create mode 100644 guava/src/com/google/common/net/InternetDomainName.java create mode 100644 guava/src/com/google/common/net/MediaType.java create mode 100644 guava/src/com/google/common/net/TldPatterns.java create mode 100644 guava/src/com/google/common/net/package-info.java create mode 100644 guava/src/com/google/common/primitives/AndroidInteger.java create mode 100644 guava/src/com/google/common/primitives/Booleans.java create mode 100644 guava/src/com/google/common/primitives/Bytes.java create mode 100644 guava/src/com/google/common/primitives/Chars.java create mode 100644 guava/src/com/google/common/primitives/Doubles.java create mode 100644 guava/src/com/google/common/primitives/Floats.java create mode 100644 guava/src/com/google/common/primitives/Ints.java create mode 100644 guava/src/com/google/common/primitives/Longs.java create mode 100644 guava/src/com/google/common/primitives/ParseRequest.java create mode 100644 guava/src/com/google/common/primitives/Primitives.java create mode 100644 guava/src/com/google/common/primitives/Shorts.java create mode 100644 guava/src/com/google/common/primitives/SignedBytes.java create mode 100644 guava/src/com/google/common/primitives/UnsignedBytes.java create mode 100644 guava/src/com/google/common/primitives/UnsignedInteger.java create mode 100644 guava/src/com/google/common/primitives/UnsignedInts.java create mode 100644 guava/src/com/google/common/primitives/UnsignedLong.java create mode 100644 guava/src/com/google/common/primitives/UnsignedLongs.java create mode 100644 guava/src/com/google/common/primitives/package-info.java create mode 100644 guava/src/com/google/common/reflect/AbstractInvocationHandler.java create mode 100644 guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java create mode 100644 guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java create mode 100644 guava/src/com/google/common/reflect/Reflection.java create mode 100644 guava/src/com/google/common/reflect/TypeCapture.java create mode 100644 guava/src/com/google/common/reflect/TypeParameter.java create mode 100644 guava/src/com/google/common/reflect/TypeResolver.java create mode 100644 guava/src/com/google/common/reflect/TypeToInstanceMap.java create mode 100644 guava/src/com/google/common/reflect/TypeToken.java create mode 100644 guava/src/com/google/common/reflect/Types.java create mode 100644 guava/src/com/google/common/reflect/package-info.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractCheckedFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractIdleService.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractScheduledService.java create mode 100644 guava/src/com/google/common/util/concurrent/AbstractService.java create mode 100644 guava/src/com/google/common/util/concurrent/AsyncFunction.java create mode 100644 guava/src/com/google/common/util/concurrent/AtomicDouble.java create mode 100644 guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java create mode 100644 guava/src/com/google/common/util/concurrent/AtomicLongMap.java create mode 100644 guava/src/com/google/common/util/concurrent/Atomics.java create mode 100644 guava/src/com/google/common/util/concurrent/Callables.java create mode 100644 guava/src/com/google/common/util/concurrent/CheckedFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java create mode 100644 guava/src/com/google/common/util/concurrent/ExecutionError.java create mode 100644 guava/src/com/google/common/util/concurrent/ExecutionList.java create mode 100644 guava/src/com/google/common/util/concurrent/FakeTimeLimiter.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingCheckedFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java create mode 100644 guava/src/com/google/common/util/concurrent/ForwardingService.java create mode 100644 guava/src/com/google/common/util/concurrent/FutureCallback.java create mode 100644 guava/src/com/google/common/util/concurrent/Futures.java create mode 100644 guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java create mode 100644 guava/src/com/google/common/util/concurrent/ListenableFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/ListenableFutureTask.java create mode 100644 guava/src/com/google/common/util/concurrent/ListeningExecutorService.java create mode 100644 guava/src/com/google/common/util/concurrent/ListeningScheduledExecutorService.java create mode 100644 guava/src/com/google/common/util/concurrent/Monitor.java create mode 100644 guava/src/com/google/common/util/concurrent/MoreExecutors.java create mode 100644 guava/src/com/google/common/util/concurrent/RateLimiter.java create mode 100644 guava/src/com/google/common/util/concurrent/Service.java create mode 100644 guava/src/com/google/common/util/concurrent/SettableFuture.java create mode 100644 guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java create mode 100644 guava/src/com/google/common/util/concurrent/Striped.java create mode 100644 guava/src/com/google/common/util/concurrent/ThreadFactoryBuilder.java create mode 100644 guava/src/com/google/common/util/concurrent/TimeLimiter.java create mode 100644 guava/src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java create mode 100644 guava/src/com/google/common/util/concurrent/UncheckedExecutionException.java create mode 100644 guava/src/com/google/common/util/concurrent/UncheckedTimeoutException.java create mode 100644 guava/src/com/google/common/util/concurrent/Uninterruptibles.java create mode 100644 guava/src/com/google/common/util/concurrent/package-info.java (limited to 'guava/src') diff --git a/guava/src/com/google/common/annotations/Beta.java b/guava/src/com/google/common/annotations/Beta.java new file mode 100644 index 0000000..5eefe9a --- /dev/null +++ b/guava/src/com/google/common/annotations/Beta.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Signifies that a public API (public class, method or field) is subject to + * incompatible changes, or even removal, in a future release. An API bearing + * this annotation is exempt from any compatibility guarantees made by its + * containing library. Note that the presence of this annotation implies nothing + * about the quality or performance of the API in question, only the fact that + * it is not "API-frozen." + * + *

It is generally safe for applications to depend on beta APIs, at + * the cost of some extra work during upgrades. However it is generally + * inadvisable for libraries (which get included on users' CLASSPATHs, + * outside the library developers' control) to do so. + * + * + * @author Kevin Bourrillion + */ +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.TYPE}) +@Documented +@GwtCompatible +public @interface Beta {} diff --git a/guava/src/com/google/common/annotations/GwtCompatible.java b/guava/src/com/google/common/annotations/GwtCompatible.java new file mode 100644 index 0000000..e8d62c0 --- /dev/null +++ b/guava/src/com/google/common/annotations/GwtCompatible.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a type indicates that the type may be + * used with the + * Google Web Toolkit (GWT). + * When applied to a method, the return type of the method is GWT compatible. + * It's useful to indicate that an instance created by factory methods has a GWT + * serializable type. In the following example, + * + *

+ * {@literal @}GwtCompatible
+ * class Lists {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   static <E> List<E> newArrayList(E... elements) {
+ *     ...
+ *   }
+ * }
+ * 
+ * The return value of {@code Lists.newArrayList(E[])} has GWT + * serializable type. It is also useful in specifying contracts of interface + * methods. In the following example, + * + *
+ * {@literal @}GwtCompatible
+ * interface ListFactory {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   <E> List<E> newArrayList(E... elements);
+ * }
+ * 
+ * The {@code newArrayList(E[])} method of all implementations of {@code + * ListFactory} is expected to return a value with a GWT serializable type. + * + *

Note that a {@code GwtCompatible} type may have some {@link + * GwtIncompatible} methods. + * + * @author Charles Fry + * @author Hayward Chan + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@GwtCompatible +public @interface GwtCompatible { + + /** + * When {@code true}, the annotated type or the type of the method return + * value is GWT serializable. + * + * @see + * Documentation about GWT serialization + */ + boolean serializable() default false; + + /** + * When {@code true}, the annotated type is emulated in GWT. The emulated + * source (also known as super-source) is different from the implementation + * used by the JVM. + * + * @see + * Documentation about GWT emulated source + */ + boolean emulated() default false; +} diff --git a/guava/src/com/google/common/annotations/GwtIncompatible.java b/guava/src/com/google/common/annotations/GwtIncompatible.java new file mode 100644 index 0000000..a56d746 --- /dev/null +++ b/guava/src/com/google/common/annotations/GwtIncompatible.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a method indicates that the method may + * not be used with the + * Google Web Toolkit (GWT), + * even though its type is annotated as {@link GwtCompatible} and accessible in + * GWT. They can cause GWT compilation errors or simply unexpected exceptions + * when used in GWT. + * + *

Note that this annotation should only be applied to methods, fields, or + * inner classes of types which are annotated as {@link GwtCompatible}. + * + * @author Charles Fry + */ +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.TYPE, ElementType.METHOD, + ElementType.CONSTRUCTOR, ElementType.FIELD }) +@Documented +@GwtCompatible +public @interface GwtIncompatible { + /** + * Describes why the annotated element is incompatible with GWT. Since this is + * generally due to a dependence on a type/method which GWT doesn't support, + * it is sufficient to simply reference the unsupported type/method. E.g. + * "Class.isInstance". + */ + String value(); +} diff --git a/guava/src/com/google/common/annotations/VisibleForTesting.java b/guava/src/com/google/common/annotations/VisibleForTesting.java new file mode 100644 index 0000000..6f867db --- /dev/null +++ b/guava/src/com/google/common/annotations/VisibleForTesting.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.annotations; + +/** + * Annotates a program element that exists, or is more widely visible than + * otherwise necessary, only for use in test code. + * + * @author Johannes Henkel + */ +@GwtCompatible +public @interface VisibleForTesting { +} diff --git a/guava/src/com/google/common/annotations/package-info.java b/guava/src/com/google/common/annotations/package-info.java new file mode 100644 index 0000000..2bf8b21 --- /dev/null +++ b/guava/src/com/google/common/annotations/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common annotation types. This package is a part of the open-source + * Guava libraries. + */ +package com.google.common.annotations; diff --git a/guava/src/com/google/common/base/Absent.java b/guava/src/com/google/common/base/Absent.java new file mode 100644 index 0000000..852fa20 --- /dev/null +++ b/guava/src/com/google/common/base/Absent.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collections; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of an {@link Optional} not containing a reference. + */ +@GwtCompatible +final class Absent extends Optional { + static final Absent INSTANCE = new Absent(); + + @Override public boolean isPresent() { + return false; + } + + @Override public Object get() { + throw new IllegalStateException("value is absent"); + } + + @Override public Object or(Object defaultValue) { + return checkNotNull(defaultValue, "use orNull() instead of or(null)"); + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override public Optional or(Optional secondChoice) { + return (Optional) checkNotNull(secondChoice); + } + + @Override public Object or(Supplier supplier) { + return checkNotNull(supplier.get(), + "use orNull() instead of a Supplier that returns null"); + } + + @Override @Nullable public Object orNull() { + return null; + } + + @Override public Set asSet() { + return Collections.emptySet(); + } + + @Override public Optional transform(Function function) { + checkNotNull(function); + return Optional.absent(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this; + } + + @Override public int hashCode() { + return 0x598df91c; + } + + @Override public String toString() { + return "Optional.absent()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/base/AbstractIterator.java b/guava/src/com/google/common/base/AbstractIterator.java new file mode 100644 index 0000000..d171af2 --- /dev/null +++ b/guava/src/com/google/common/base/AbstractIterator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Note this class is a copy of + * {@link com.google.common.collect.AbstractIterator} (for dependency reasons). + */ +@GwtCompatible +abstract class AbstractIterator implements Iterator { + private State state = State.NOT_READY; + + protected AbstractIterator() {} + + private enum State { + READY, NOT_READY, DONE, FAILED, + } + + private T next; + + protected abstract T computeNext(); + + protected final T endOfData() { + state = State.DONE; + return null; + } + + @Override + public final boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + return next; + } + + @Override public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/base/Ascii.java b/guava/src/com/google/common/base/Ascii.java new file mode 100644 index 0000000..792856d --- /dev/null +++ b/guava/src/com/google/common/base/Ascii.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Static methods pertaining to ASCII characters (those in the range of values + * {@code 0x00} through {@code 0x7F}), and to strings containing such + * characters. + * + *

ASCII utilities also exist in other classes of this package: + *

    + * + *
  • {@link Charsets#US_ASCII} specifies the {@code Charset} of ASCII characters. + *
  • {@link CharMatcher#ASCII} matches ASCII characters and provides text processing methods + * which operate only on the ASCII characters of a string. + *
+ * + * @author Craig Berry + * @author Gregory Kick + * @since 7.0 + */ +@GwtCompatible +public final class Ascii { + + private Ascii() {} + + /* The ASCII control characters, per RFC 20. */ + /** + * Null ('\0'): The all-zeros character which may serve to accomplish + * time fill and media fill. Normally used as a C string terminator. + *

Although RFC 20 names this as "Null", note that it is distinct + * from the C/C++ "NULL" pointer. + * + * @since 8.0 + */ + public static final byte NUL = 0; + + /** + * Start of Heading: A communication control character used at + * the beginning of a sequence of characters which constitute a + * machine-sensible address or routing information. Such a sequence is + * referred to as the "heading." An STX character has the effect of + * terminating a heading. + * + * @since 8.0 + */ + public static final byte SOH = 1; + + /** + * Start of Text: A communication control character which + * precedes a sequence of characters that is to be treated as an entity + * and entirely transmitted through to the ultimate destination. Such a + * sequence is referred to as "text." STX may be used to terminate a + * sequence of characters started by SOH. + * + * @since 8.0 + */ + public static final byte STX = 2; + + /** + * End of Text: A communication control character used to + * terminate a sequence of characters started with STX and transmitted + * as an entity. + * + * @since 8.0 + */ + public static final byte ETX = 3; + + /** + * End of Transmission: A communication control character used + * to indicate the conclusion of a transmission, which may have + * contained one or more texts and any associated headings. + * + * @since 8.0 + */ + public static final byte EOT = 4; + + /** + * Enquiry: A communication control character used in data + * communication systems as a request for a response from a remote + * station. It may be used as a "Who Are You" (WRU) to obtain + * identification, or may be used to obtain station status, or both. + * + * @since 8.0 + */ + public static final byte ENQ = 5; + + /** + * Acknowledge: A communication control character transmitted + * by a receiver as an affirmative response to a sender. + * + * @since 8.0 + */ + public static final byte ACK = 6; + + /** + * Bell ('\a'): A character for use when there is a need to call for + * human attention. It may control alarm or attention devices. + * + * @since 8.0 + */ + public static final byte BEL = 7; + + /** + * Backspace ('\b'): A format effector which controls the movement of + * the printing position one printing space backward on the same + * printing line. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte BS = 8; + + /** + * Horizontal Tabulation ('\t'): A format effector which controls the + * movement of the printing position to the next in a series of + * predetermined positions along the printing line. (Applicable also to + * display devices and the skip function on punched cards.) + * + * @since 8.0 + */ + public static final byte HT = 9; + + /** + * Line Feed ('\n'): A format effector which controls the movement of + * the printing position to the next printing line. (Applicable also to + * display devices.) Where appropriate, this character may have the + * meaning "New Line" (NL), a format effector which controls the + * movement of the printing point to the first printing position on the + * next printing line. Use of this convention requires agreement + * between sender and recipient of data. + * + * @since 8.0 + */ + public static final byte LF = 10; + + /** + * Alternate name for {@link #LF}. ({@code LF} is preferred.) + * + * @since 8.0 + */ + public static final byte NL = 10; + + /** + * Vertical Tabulation ('\v'): A format effector which controls the + * movement of the printing position to the next in a series of + * predetermined printing lines. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte VT = 11; + + /** + * Form Feed ('\f'): A format effector which controls the movement of + * the printing position to the first pre-determined printing line on + * the next form or page. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte FF = 12; + + /** + * Carriage Return ('\r'): A format effector which controls the + * movement of the printing position to the first printing position on + * the same printing line. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte CR = 13; + + /** + * Shift Out: A control character indicating that the code + * combinations which follow shall be interpreted as outside of the + * character set of the standard code table until a Shift In character + * is reached. + * + * @since 8.0 + */ + public static final byte SO = 14; + + /** + * Shift In: A control character indicating that the code + * combinations which follow shall be interpreted according to the + * standard code table. + * + * @since 8.0 + */ + public static final byte SI = 15; + + /** + * Data Link Escape: A communication control character which + * will change the meaning of a limited number of contiguously following + * characters. It is used exclusively to provide supplementary controls + * in data communication networks. + * + * @since 8.0 + */ + public static final byte DLE = 16; + + /** + * Device Control 1. Characters for the control + * of ancillary devices associated with data processing or + * telecommunication systems, more especially switching devices "on" or + * "off." (If a single "stop" control is required to interrupt or turn + * off ancillary devices, DC4 is the preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC1 = 17; // aka XON + + /** + * Transmission On: Although originally defined as DC1, this ASCII + * control character is now better known as the XON code used for software + * flow control in serial communications. The main use is restarting + * the transmission after the communication has been stopped by the XOFF + * control code. + * + * @since 8.0 + */ + public static final byte XON = 17; // aka DC1 + + /** + * Device Control 2. Characters for the control + * of ancillary devices associated with data processing or + * telecommunication systems, more especially switching devices "on" or + * "off." (If a single "stop" control is required to interrupt or turn + * off ancillary devices, DC4 is the preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC2 = 18; + + /** + * Device Control 3. Characters for the control + * of ancillary devices associated with data processing or + * telecommunication systems, more especially switching devices "on" or + * "off." (If a single "stop" control is required to interrupt or turn + * off ancillary devices, DC4 is the preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC3 = 19; // aka XOFF + + /** + * Transmission off. See {@link #XON} for explanation. + * + * @since 8.0 + */ + public static final byte XOFF = 19; // aka DC3 + + /** + * Device Control 4. Characters for the control + * of ancillary devices associated with data processing or + * telecommunication systems, more especially switching devices "on" or + * "off." (If a single "stop" control is required to interrupt or turn + * off ancillary devices, DC4 is the preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC4 = 20; + + /** + * Negative Acknowledge: A communication control character + * transmitted by a receiver as a negative response to the sender. + * + * @since 8.0 + */ + public static final byte NAK = 21; + + /** + * Synchronous Idle: A communication control character used by + * a synchronous transmission system in the absence of any other + * character to provide a signal from which synchronism may be achieved + * or retained. + * + * @since 8.0 + */ + public static final byte SYN = 22; + + /** + * End of Transmission Block: A communication control character + * used to indicate the end of a block of data for communication + * purposes. ETB is used for blocking data where the block structure is + * not necessarily related to the processing format. + * + * @since 8.0 + */ + public static final byte ETB = 23; + + /** + * Cancel: A control character used to indicate that the data + * with which it is sent is in error or is to be disregarded. + * + * @since 8.0 + */ + public static final byte CAN = 24; + + /** + * End of Medium: A control character associated with the sent + * data which may be used to identify the physical end of the medium, or + * the end of the used, or wanted, portion of information recorded on a + * medium. (The position of this character does not necessarily + * correspond to the physical end of the medium.) + * + * @since 8.0 + */ + public static final byte EM = 25; + + /** + * Substitute: A character that may be substituted for a + * character which is determined to be invalid or in error. + * + * @since 8.0 + */ + public static final byte SUB = 26; + + /** + * Escape: A control character intended to provide code + * extension (supplementary characters) in general information + * interchange. The Escape character itself is a prefix affecting the + * interpretation of a limited number of contiguously following + * characters. + * + * @since 8.0 + */ + public static final byte ESC = 27; + + /** + * File Separator: These four information separators may be + * used within data in optional fashion, except that their hierarchical + * relationship shall be: FS is the most inclusive, then GS, then RS, + * and US is least inclusive. (The content and length of a File, Group, + * Record, or Unit are not specified.) + * + * @since 8.0 + */ + public static final byte FS = 28; + + /** + * Group Separator: These four information separators may be + * used within data in optional fashion, except that their hierarchical + * relationship shall be: FS is the most inclusive, then GS, then RS, + * and US is least inclusive. (The content and length of a File, Group, + * Record, or Unit are not specified.) + * + * @since 8.0 + */ + public static final byte GS = 29; + + /** + * Record Separator: These four information separators may be + * used within data in optional fashion, except that their hierarchical + * relationship shall be: FS is the most inclusive, then GS, then RS, + * and US is least inclusive. (The content and length of a File, Group, + * Record, or Unit are not specified.) + * + * @since 8.0 + */ + public static final byte RS = 30; + + /** + * Unit Separator: These four information separators may be + * used within data in optional fashion, except that their hierarchical + * relationship shall be: FS is the most inclusive, then GS, then RS, + * and US is least inclusive. (The content and length of a File, Group, + * Record, or Unit are not specified.) + * + * @since 8.0 + */ + public static final byte US = 31; + + /** + * Space: A normally non-printing graphic character used to + * separate words. It is also a format effector which controls the + * movement of the printing position, one printing position forward. + * (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte SP = 32; + + /** + * Alternate name for {@link #SP}. + * + * @since 8.0 + */ + public static final byte SPACE = 32; + + /** + * Delete: This character is used primarily to "erase" or + * "obliterate" erroneous or unwanted characters in perforated tape. + * + * @since 8.0 + */ + public static final byte DEL = 127; + + /** + * The minimum value of an ASCII character. + * + * @since 9.0 (was type {@code int} before 12.0) + */ + public static final char MIN = 0; + + /** + * The maximum value of an ASCII character. + * + * @since 9.0 (was type {@code int} before 12.0) + */ + public static final char MAX = 127; + + /** + * Returns a copy of the input string in which all {@linkplain #isUpperCase(char) uppercase ASCII + * characters} have been converted to lowercase. All other characters are copied without + * modification. + */ + public static String toLowerCase(String string) { + int length = string.length(); + StringBuilder builder = new StringBuilder(length); + for (int i = 0; i < length; i++) { + builder.append(toLowerCase(string.charAt(i))); + } + return builder.toString(); + } + + /** + * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character} returns the + * lowercase equivalent. Otherwise returns the argument. + */ + public static char toLowerCase(char c) { + return isUpperCase(c) ? (char) (c ^ 0x20) : c; + } + + /** + * Returns a copy of the input string in which all {@linkplain #isLowerCase(char) lowercase ASCII + * characters} have been converted to uppercase. All other characters are copied without + * modification. + */ + public static String toUpperCase(String string) { + int length = string.length(); + StringBuilder builder = new StringBuilder(length); + for (int i = 0; i < length; i++) { + builder.append(toUpperCase(string.charAt(i))); + } + return builder.toString(); + } + + /** + * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character} returns the + * uppercase equivalent. Otherwise returns the argument. + */ + public static char toUpperCase(char c) { + return isLowerCase(c) ? (char) (c & 0x5f) : c; + } + + /** + * Indicates whether {@code c} is one of the twenty-six lowercase ASCII alphabetic characters + * between {@code 'a'} and {@code 'z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isLowerCase(char c) { + return (c >= 'a') && (c <= 'z'); + } + + /** + * Indicates whether {@code c} is one of the twenty-six uppercase ASCII alphabetic characters + * between {@code 'A'} and {@code 'Z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isUpperCase(char c) { + return (c >= 'A') && (c <= 'Z'); + } +} diff --git a/guava/src/com/google/common/base/CaseFormat.java b/guava/src/com/google/common/base/CaseFormat.java new file mode 100644 index 0000000..8ef7c5c --- /dev/null +++ b/guava/src/com/google/common/base/CaseFormat.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Utility class for converting between various ASCII case formats. + * + * @author Mike Bostock + * @since 1.0 + */ +@GwtCompatible +public enum CaseFormat { + /** + * Hyphenated variable naming convention, e.g., "lower-hyphen". + */ + LOWER_HYPHEN(CharMatcher.is('-'), "-"), + + /** + * C++ variable naming convention, e.g., "lower_underscore". + */ + LOWER_UNDERSCORE(CharMatcher.is('_'), "_"), + + /** + * Java variable naming convention, e.g., "lowerCamel". + */ + LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), ""), + + /** + * Java and C++ class naming convention, e.g., "UpperCamel". + */ + UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), ""), + + /** + * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE". + */ + UPPER_UNDERSCORE(CharMatcher.is('_'), "_"); + + private final CharMatcher wordBoundary; + private final String wordSeparator; + + CaseFormat(CharMatcher wordBoundary, String wordSeparator) { + this.wordBoundary = wordBoundary; + this.wordSeparator = wordSeparator; + } + + /** + * Converts the specified {@code String s} from this format to the specified {@code format}. A + * "best effort" approach is taken; if {@code s} does not conform to the assumed format, then the + * behavior of this method is undefined but we make a reasonable effort at converting anyway. + */ + public String to(CaseFormat format, String s) { + if (format == null) { + throw new NullPointerException(); + } + if (s == null) { + throw new NullPointerException(); + } + + if (format == this) { + return s; + } + + /* optimize cases where no camel conversion is required */ + switch (this) { + case LOWER_HYPHEN: + switch (format) { + case LOWER_UNDERSCORE: + return s.replace('-', '_'); + case UPPER_UNDERSCORE: + return Ascii.toUpperCase(s.replace('-', '_')); + } + break; + case LOWER_UNDERSCORE: + switch (format) { + case LOWER_HYPHEN: + return s.replace('_', '-'); + case UPPER_UNDERSCORE: + return Ascii.toUpperCase(s); + } + break; + case UPPER_UNDERSCORE: + switch (format) { + case LOWER_HYPHEN: + return Ascii.toLowerCase(s.replace('_', '-')); + case LOWER_UNDERSCORE: + return Ascii.toLowerCase(s); + } + break; + } + + // otherwise, deal with camel conversion + StringBuilder out = null; + int i = 0; + int j = -1; + while ((j = wordBoundary.indexIn(s, ++j)) != -1) { + if (i == 0) { + // include some extra space for separators + out = new StringBuilder(s.length() + 4 * wordSeparator.length()); + out.append(format.normalizeFirstWord(s.substring(i, j))); + } else { + out.append(format.normalizeWord(s.substring(i, j))); + } + out.append(format.wordSeparator); + i = j + wordSeparator.length(); + } + if (i == 0) { + return format.normalizeFirstWord(s); + } + out.append(format.normalizeWord(s.substring(i))); + return out.toString(); + } + + private String normalizeFirstWord(String word) { + switch (this) { + case LOWER_CAMEL: + return Ascii.toLowerCase(word); + default: + return normalizeWord(word); + } + } + + private String normalizeWord(String word) { + switch (this) { + case LOWER_HYPHEN: + return Ascii.toLowerCase(word); + case LOWER_UNDERSCORE: + return Ascii.toLowerCase(word); + case LOWER_CAMEL: + return firstCharOnlyToUpper(word); + case UPPER_CAMEL: + return firstCharOnlyToUpper(word); + case UPPER_UNDERSCORE: + return Ascii.toUpperCase(word); + } + throw new RuntimeException("unknown case: " + this); + } + + private static String firstCharOnlyToUpper(String word) { + int length = word.length(); + if (length == 0) { + return word; + } + return new StringBuilder(length) + .append(Ascii.toUpperCase(word.charAt(0))) + .append(Ascii.toLowerCase(word.substring(1))) + .toString(); + } +} diff --git a/guava/src/com/google/common/base/CharMatcher.java b/guava/src/com/google/common/base/CharMatcher.java new file mode 100644 index 0000000..64a70b7 --- /dev/null +++ b/guava/src/com/google/common/base/CharMatcher.java @@ -0,0 +1,1271 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Arrays; +import javax.annotation.CheckReturnValue; + +/** + * Determines a true or false value for any Java {@code char} value, just as {@link Predicate} does + * for any {@link Object}. Also offers basic text processing methods based on this function. + * Implementations are strongly encouraged to be side-effect-free and immutable. + * + *

Throughout the documentation of this class, the phrase "matching character" is used to mean + * "any character {@code c} for which {@code this.matches(c)} returns {@code true}". + * + *

Note: This class deals only with {@code char} values; it does not understand + * supplementary Unicode code points in the range {@code 0x10000} to {@code 0x10FFFF}. Such logical + * characters are encoded into a {@code String} using surrogate pairs, and a {@code CharMatcher} + * treats these just as two separate characters. + * + *

Example usages:

+ *   String trimmed = {@link #WHITESPACE WHITESPACE}.{@link #trimFrom trimFrom}(userInput);
+ *   if ({@link #ASCII ASCII}.{@link #matchesAllOf matchesAllOf}(s)) { ... }
+ * + *

See the Guava User Guide article on + * {@code CharMatcher}. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@Beta // Possibly change from chars to code points; decide constants vs. methods +@GwtCompatible +public abstract class CharMatcher implements Predicate { + // Constants + /** + * Determines whether a character is a breaking whitespace (that is, a whitespace which can be + * interpreted as a break between words for formatting purposes). See {@link #WHITESPACE} for a + * discussion of that term. + * + * @since 2.0 + */ + public static final CharMatcher BREAKING_WHITESPACE = + anyOf("\t\n\013\f\r \u0085\u1680\u2028\u2029\u205f\u3000") + .or(inRange('\u2000', '\u2006')) + .or(inRange('\u2008', '\u200a')) + .withToString("CharMatcher.BREAKING_WHITESPACE") + .precomputed(); + + /** + * Determines whether a character is ASCII, meaning that its code point is less than 128. + */ + public static final CharMatcher ASCII = inRange('\0', '\u007f', "CharMatcher.ASCII"); + + /** + * Determines whether a character is a digit according to + * Unicode. + */ + public static final CharMatcher DIGIT; + + static { + CharMatcher digit = inRange('0', '9'); + String zeroes = + "\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6\u0c66" + + "\u0ce6\u0d66\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u1810\u1946" + + "\u19d0\u1b50\u1bb0\u1c40\u1c50\ua620\ua8d0\ua900\uaa50\uff10"; + for (char base : zeroes.toCharArray()) { + digit = digit.or(inRange(base, (char) (base + 9))); + } + DIGIT = digit.withToString("CharMatcher.DIGIT").precomputed(); + } + + /** + * Determines whether a character is a digit according to {@link Character#isDigit(char) Java's + * definition}. If you only care to match ASCII digits, you can use {@code inRange('0', '9')}. + */ + public static final CharMatcher JAVA_DIGIT = new CharMatcher("CharMatcher.JAVA_DIGIT") { + @Override public boolean matches(char c) { + return Character.isDigit(c); + } + }; + + /** + * Determines whether a character is a letter according to {@link Character#isLetter(char) Java's + * definition}. If you only care to match letters of the Latin alphabet, you can use {@code + * inRange('a', 'z').or(inRange('A', 'Z'))}. + */ + public static final CharMatcher JAVA_LETTER = new CharMatcher("CharMatcher.JAVA_LETTER") { + @Override public boolean matches(char c) { + return Character.isLetter(c); + } + + @Override public CharMatcher precomputed() { + return this; + } + }; + + /** + * Determines whether a character is a letter or digit according to {@link + * Character#isLetterOrDigit(char) Java's definition}. + */ + public static final CharMatcher JAVA_LETTER_OR_DIGIT = + new CharMatcher("CharMatcher.JAVA_LETTER_OR_DIGIT") { + @Override public boolean matches(char c) { + return Character.isLetterOrDigit(c); + } + }; + + /** + * Determines whether a character is upper case according to {@link Character#isUpperCase(char) + * Java's definition}. + */ + public static final CharMatcher JAVA_UPPER_CASE = + new CharMatcher("CharMatcher.JAVA_UPPER_CASE") { + @Override public boolean matches(char c) { + return Character.isUpperCase(c); + } + }; + + /** + * Determines whether a character is lower case according to {@link Character#isLowerCase(char) + * Java's definition}. + */ + public static final CharMatcher JAVA_LOWER_CASE = + new CharMatcher("CharMatcher.JAVA_LOWER_CASE") { + @Override public boolean matches(char c) { + return Character.isLowerCase(c); + } + }; + + /** + * Determines whether a character is an ISO control character as specified by {@link + * Character#isISOControl(char)}. + */ + public static final CharMatcher JAVA_ISO_CONTROL = + inRange('\u0000', '\u001f') + .or(inRange('\u007f', '\u009f')) + .withToString("CharMatcher.JAVA_ISO_CONTROL"); + + /** + * Determines whether a character is invisible; that is, if its Unicode category is any of + * SPACE_SEPARATOR, LINE_SEPARATOR, PARAGRAPH_SEPARATOR, CONTROL, FORMAT, SURROGATE, and + * PRIVATE_USE according to ICU4J. + */ + public static final CharMatcher INVISIBLE = inRange('\u0000', '\u0020') + .or(inRange('\u007f', '\u00a0')) + .or(is('\u00ad')) + .or(inRange('\u0600', '\u0604')) + .or(anyOf("\u06dd\u070f\u1680\u180e")) + .or(inRange('\u2000', '\u200f')) + .or(inRange('\u2028', '\u202f')) + .or(inRange('\u205f', '\u2064')) + .or(inRange('\u206a', '\u206f')) + .or(is('\u3000')) + .or(inRange('\ud800', '\uf8ff')) + .or(anyOf("\ufeff\ufff9\ufffa\ufffb")) + .withToString("CharMatcher.INVISIBLE") + .precomputed(); + + /** + * Determines whether a character is single-width (not double-width). When in doubt, this matcher + * errs on the side of returning {@code false} (that is, it tends to assume a character is + * double-width). + * + *

Note: as the reference file evolves, we will modify this constant to keep it up to + * date. + */ + public static final CharMatcher SINGLE_WIDTH = inRange('\u0000', '\u04f9') + .or(is('\u05be')) + .or(inRange('\u05d0', '\u05ea')) + .or(is('\u05f3')) + .or(is('\u05f4')) + .or(inRange('\u0600', '\u06ff')) + .or(inRange('\u0750', '\u077f')) + .or(inRange('\u0e00', '\u0e7f')) + .or(inRange('\u1e00', '\u20af')) + .or(inRange('\u2100', '\u213a')) + .or(inRange('\ufb50', '\ufdff')) + .or(inRange('\ufe70', '\ufeff')) + .or(inRange('\uff61', '\uffdc')) + .withToString("CharMatcher.SINGLE_WIDTH") + .precomputed(); + + /** Matches any character. */ + public static final CharMatcher ANY = + new CharMatcher("CharMatcher.ANY") { + @Override public boolean matches(char c) { + return true; + } + + @Override public int indexIn(CharSequence sequence) { + return (sequence.length() == 0) ? -1 : 0; + } + + @Override public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + Preconditions.checkPositionIndex(start, length); + return (start == length) ? -1 : start; + } + + @Override public int lastIndexIn(CharSequence sequence) { + return sequence.length() - 1; + } + + @Override public boolean matchesAllOf(CharSequence sequence) { + checkNotNull(sequence); + return true; + } + + @Override public boolean matchesNoneOf(CharSequence sequence) { + return sequence.length() == 0; + } + + @Override public String removeFrom(CharSequence sequence) { + checkNotNull(sequence); + return ""; + } + + @Override public String replaceFrom(CharSequence sequence, char replacement) { + char[] array = new char[sequence.length()]; + Arrays.fill(array, replacement); + return new String(array); + } + + @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) { + StringBuilder retval = new StringBuilder(sequence.length() * replacement.length()); + for (int i = 0; i < sequence.length(); i++) { + retval.append(replacement); + } + return retval.toString(); + } + + @Override public String collapseFrom(CharSequence sequence, char replacement) { + return (sequence.length() == 0) ? "" : String.valueOf(replacement); + } + + @Override public String trimFrom(CharSequence sequence) { + checkNotNull(sequence); + return ""; + } + + @Override public int countIn(CharSequence sequence) { + return sequence.length(); + } + + @Override public CharMatcher and(CharMatcher other) { + return checkNotNull(other); + } + + @Override public CharMatcher or(CharMatcher other) { + checkNotNull(other); + return this; + } + + @Override public CharMatcher negate() { + return NONE; + } + + @Override public CharMatcher precomputed() { + return this; + } + }; + + /** Matches no characters. */ + public static final CharMatcher NONE = + new CharMatcher("CharMatcher.NONE") { + @Override public boolean matches(char c) { + return false; + } + + @Override public int indexIn(CharSequence sequence) { + checkNotNull(sequence); + return -1; + } + + @Override public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + Preconditions.checkPositionIndex(start, length); + return -1; + } + + @Override public int lastIndexIn(CharSequence sequence) { + checkNotNull(sequence); + return -1; + } + + @Override public boolean matchesAllOf(CharSequence sequence) { + return sequence.length() == 0; + } + + @Override public boolean matchesNoneOf(CharSequence sequence) { + checkNotNull(sequence); + return true; + } + + @Override public String removeFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override public String replaceFrom(CharSequence sequence, char replacement) { + return sequence.toString(); + } + + @Override public String replaceFrom(CharSequence sequence, CharSequence replacement) { + checkNotNull(replacement); + return sequence.toString(); + } + + @Override public String collapseFrom(CharSequence sequence, char replacement) { + return sequence.toString(); + } + + @Override public String trimFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override public int countIn(CharSequence sequence) { + checkNotNull(sequence); + return 0; + } + + @Override public CharMatcher and(CharMatcher other) { + checkNotNull(other); + return this; + } + + @Override public CharMatcher or(CharMatcher other) { + return checkNotNull(other); + } + + @Override public CharMatcher negate() { + return ANY; + } + + @Override void setBits(LookupTable table) {} + + @Override public CharMatcher precomputed() { + return this; + } + }; + + // Static factories + + /** + * Returns a {@code char} matcher that matches only one specified character. + */ + public static CharMatcher is(final char match) { + String description = new StringBuilder("CharMatcher.is(") + .append(Integer.toHexString(match)) + .append(")") + .toString(); + return new CharMatcher(description) { + @Override public boolean matches(char c) { + return c == match; + } + + @Override public String replaceFrom(CharSequence sequence, char replacement) { + return sequence.toString().replace(match, replacement); + } + + @Override public CharMatcher and(CharMatcher other) { + return other.matches(match) ? this : NONE; + } + + @Override public CharMatcher or(CharMatcher other) { + return other.matches(match) ? other : super.or(other); + } + + @Override public CharMatcher negate() { + return isNot(match); + } + + @Override void setBits(LookupTable table) { + table.set(match); + } + + @Override public CharMatcher precomputed() { + return this; + } + }; + } + + /** + * Returns a {@code char} matcher that matches any character except the one specified. + * + *

To negate another {@code CharMatcher}, use {@link #negate()}. + */ + public static CharMatcher isNot(final char match) { + String description = new StringBuilder("CharMatcher.isNot(") + .append(Integer.toHexString(match)) + .append(")") + .toString(); + return new CharMatcher(description) { + @Override public boolean matches(char c) { + return c != match; + } + + @Override public CharMatcher and(CharMatcher other) { + return other.matches(match) ? super.and(other) : other; + } + + @Override public CharMatcher or(CharMatcher other) { + return other.matches(match) ? ANY : this; + } + + @Override public CharMatcher negate() { + return is(match); + } + }; + } + + /** + * Returns a {@code char} matcher that matches any character present in the given character + * sequence. + */ + public static CharMatcher anyOf(final CharSequence sequence) { + switch (sequence.length()) { + case 0: + return NONE; + case 1: + return is(sequence.charAt(0)); + case 2: + final char match1 = sequence.charAt(0); + final char match2 = sequence.charAt(1); + return new CharMatcher( + new StringBuilder("CharMatcher.anyOf(\"").append(sequence).append("\")").toString()) { + @Override public boolean matches(char c) { + return c == match1 || c == match2; + } + + @Override void setBits(LookupTable table) { + table.set(match1); + table.set(match2); + } + + @Override public CharMatcher precomputed() { + return this; + } + }; + } + final char[] chars = sequence.toString().toCharArray(); + Arrays.sort(chars); + + return new CharMatcher(new StringBuilder("CharMatcher.anyOf(\"").append(chars) + .append("\")").toString()) { + @Override public boolean matches(char c) { + return Arrays.binarySearch(chars, c) >= 0; + } + }; + } + + /** + * Returns a {@code char} matcher that matches any character not present in the given character + * sequence. + */ + public static CharMatcher noneOf(CharSequence sequence) { + return anyOf(sequence).negate(); + } + + /** + * Returns a {@code char} matcher that matches any character in a given range (both endpoints are + * inclusive). For example, to match any lowercase letter of the English alphabet, use {@code + * CharMatcher.inRange('a', 'z')}. + * + * @throws IllegalArgumentException if {@code endInclusive < startInclusive} + */ + public static CharMatcher inRange(final char startInclusive, final char endInclusive) { + checkArgument(endInclusive >= startInclusive); + String description = new StringBuilder("CharMatcher.inRange(") + .append(Integer.toHexString(startInclusive)) + .append(", ") + .append(Integer.toHexString(endInclusive)) + .append(")") + .toString(); + return inRange(startInclusive, endInclusive, description); + } + + static CharMatcher inRange(final char startInclusive, final char endInclusive, + String description) { + return new CharMatcher(description) { + @Override public boolean matches(char c) { + return startInclusive <= c && c <= endInclusive; + } + + @Override void setBits(LookupTable table) { + char c = startInclusive; + while (true) { + table.set(c); + if (c++ == endInclusive) { + break; + } + } + } + + @Override public CharMatcher precomputed() { + return this; + } + }; + } + + /** + * Returns a matcher with identical behavior to the given {@link Character}-based predicate, but + * which operates on primitive {@code char} instances instead. + */ + public static CharMatcher forPredicate(final Predicate predicate) { + checkNotNull(predicate); + if (predicate instanceof CharMatcher) { + return (CharMatcher) predicate; + } + String description = new StringBuilder("CharMatcher.forPredicate(") + .append(predicate) + .append(')') + .toString(); + return new CharMatcher(description) { + @Override public boolean matches(char c) { + return predicate.apply(c); + } + + @Override public boolean apply(Character character) { + return predicate.apply(checkNotNull(character)); + } + }; + } + + // State + final String description; + + // Constructors + + /** + * Sets the {@code toString()} from the given description. + */ + CharMatcher(String description) { + this.description = description; + } + + /** + * Constructor for use by subclasses. When subclassing, you may want to override + * {@code toString()} to provide a useful description. + */ + protected CharMatcher() { + description = "UnknownCharMatcher"; + } + + // Abstract methods + + /** Determines a true or false value for the given character. */ + public abstract boolean matches(char c); + + // Non-static factories + + /** + * Returns a matcher that matches any character not matched by this matcher. + */ + public CharMatcher negate() { + final CharMatcher original = this; + return new CharMatcher(original + ".negate()") { + @Override public boolean matches(char c) { + return !original.matches(c); + } + + @Override public boolean matchesAllOf(CharSequence sequence) { + return original.matchesNoneOf(sequence); + } + + @Override public boolean matchesNoneOf(CharSequence sequence) { + return original.matchesAllOf(sequence); + } + + @Override public int countIn(CharSequence sequence) { + return sequence.length() - original.countIn(sequence); + } + + @Override public CharMatcher negate() { + return original; + } + }; + } + + /** + * Returns a matcher that matches any character matched by both this matcher and {@code other}. + */ + public CharMatcher and(CharMatcher other) { + return new And(this, checkNotNull(other)); + } + + private static class And extends CharMatcher { + final CharMatcher first; + final CharMatcher second; + + And(CharMatcher a, CharMatcher b) { + this(a, b, "CharMatcher.and(" + a + ", " + b + ")"); + } + + And(CharMatcher a, CharMatcher b, String description) { + super(description); + first = checkNotNull(a); + second = checkNotNull(b); + } + + @Override + public CharMatcher and(CharMatcher other) { + return new And(this, other); + } + + @Override + public boolean matches(char c) { + return first.matches(c) && second.matches(c); + } + + @Override + CharMatcher withToString(String description) { + return new And(first, second, description); + } + } + + /** + * Returns a matcher that matches any character matched by either this matcher or {@code other}. + */ + public CharMatcher or(CharMatcher other) { + return new Or(this, checkNotNull(other)); + } + + private static class Or extends CharMatcher { + final CharMatcher first; + final CharMatcher second; + + Or(CharMatcher a, CharMatcher b, String description) { + super(description); + first = checkNotNull(a); + second = checkNotNull(b); + } + + Or(CharMatcher a, CharMatcher b) { + this(a, b, "CharMatcher.or(" + a + ", " + b + ")"); + } + + @Override + public CharMatcher or(CharMatcher other) { + return new Or(this, checkNotNull(other)); + } + + @Override + public boolean matches(char c) { + return first.matches(c) || second.matches(c); + } + + @Override + CharMatcher withToString(String description) { + return new Or(first, second, description); + } + } + + /** + * Returns a {@code char} matcher functionally equivalent to this one, but which may be faster to + * query than the original; your mileage may vary. Precomputation takes time and is likely to be + * worthwhile only if the precomputed matcher is queried many thousands of times. + * + *

This method has no effect (returns {@code this}) when called in GWT: it's unclear whether a + * precomputed matcher is faster, but it certainly consumes more memory, which doesn't seem like a + * worthwhile tradeoff in a browser. + */ + public CharMatcher precomputed() { + return Platform.precomputeCharMatcher(this); + } + + /** + * Construct an array of all possible chars in the slowest way possible. + */ + char[] slowGetChars() { + char[] allChars = new char[65536]; + int size = 0; + for (int c = Character.MIN_VALUE; c <= Character.MAX_VALUE; c++) { + if (matches((char) c)) { + allChars[size++] = (char) c; + } + } + char[] retValue = new char[size]; + System.arraycopy(allChars, 0, retValue, 0, size); + return retValue; + } + + /** + * This is the actual implementation of {@link #precomputed}, but we bounce calls through a method + * on {@link Platform} so that we can have different behavior in GWT. + * + *

If the number of matched characters is small enough, we try to build a small hash + * table to contain all of the characters. Otherwise, we record the characters in eight-kilobyte + * bit array. In many situations this produces a matcher which is faster to query + * than the original. + */ + CharMatcher precomputedInternal() { + final char[] chars = slowGetChars(); + int totalCharacters = chars.length; + if (totalCharacters == 0) { + return NONE; + } else if (totalCharacters == 1) { + return is(chars[0]); + } else if (totalCharacters < SmallCharMatcher.MAX_SIZE) { + return SmallCharMatcher.from(chars, toString()); + } else if (totalCharacters < MediumCharMatcher.MAX_SIZE) { + return MediumCharMatcher.from(chars, toString()); + } + // Otherwise, make the full lookup table. + final LookupTable table = new LookupTable(); + setBits(table); + final CharMatcher outer = this; + + return new CharMatcher(outer.toString()) { + @Override public boolean matches(char c) { + return table.get(c); + } + + // TODO(kevinb): make methods like negate() smart? + + @Override public CharMatcher precomputed() { + return this; + } + }; + } + + /** + * Subclasses should provide a new CharMatcher with the same characteristics as {@code this}, + * but with their {@code toString} method overridden with the new description. + * + *

This is unsupported by default. + */ + CharMatcher withToString(String description) { + throw new UnsupportedOperationException(); + + } + + /** + * For use by implementors; sets the bit corresponding to each character ('\0' to '{@literal + * \}uFFFF') that matches this matcher in the given bit array, leaving all other bits untouched. + * + *

The default implementation loops over every possible character value, invoking {@link + * #matches} for each one. + */ + void setBits(LookupTable table) { + char c = Character.MIN_VALUE; + while (true) { + if (matches(c)) { + table.set(c); + } + if (c++ == Character.MAX_VALUE) { + break; + } + } + } + + /** + * A bit array with one bit per {@code char} value, used by {@link CharMatcher#precomputed}. + * + *

TODO(kevinb): possibly share a common BitArray class with BloomFilter and others... a + * simpler java.util.BitSet. + */ + private static final class LookupTable { + int[] data = new int[2048]; + + void set(char index) { + data[index >> 5] |= (1 << index); + } + + boolean get(char index) { + return (data[index >> 5] & (1 << index)) != 0; + } + } + + // Text processing routines + + /** + * Returns {@code true} if a character sequence contains at least one matching character. + * Equivalent to {@code !matchesNoneOf(sequence)}. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code true} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches at least one character in the sequence + * @since 8.0 + */ + public boolean matchesAnyOf(CharSequence sequence) { + return !matchesNoneOf(sequence); + } + + /** + * Returns {@code true} if a character sequence contains only matching characters. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code false} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches every character in the sequence, including when + * the sequence is empty + */ + public boolean matchesAllOf(CharSequence sequence) { + for (int i = sequence.length() - 1; i >= 0; i--) { + if (!matches(sequence.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if a character sequence contains no matching characters. Equivalent to + * {@code !matchesAnyOf(sequence)}. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code false} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches every character in the sequence, including when + * the sequence is empty + */ + public boolean matchesNoneOf(CharSequence sequence) { + return indexIn(sequence) == -1; + } + + /** + * Returns the index of the first matching character in a character sequence, or {@code -1} if no + * matching character is present. + * + *

The default implementation iterates over the sequence in forward order calling {@link + * #matches} for each character. + * + * @param sequence the character sequence to examine from the beginning + * @return an index, or {@code -1} if no character matches + */ + public int indexIn(CharSequence sequence) { + int length = sequence.length(); + for (int i = 0; i < length; i++) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the first matching character in a character sequence, starting from a + * given position, or {@code -1} if no character matches after that position. + * + *

The default implementation iterates over the sequence in forward order, beginning at {@code + * start}, calling {@link #matches} for each character. + * + * @param sequence the character sequence to examine + * @param start the first index to examine; must be nonnegative and no greater than {@code + * sequence.length()} + * @return the index of the first matching character, guaranteed to be no less than {@code start}, + * or {@code -1} if no character matches + * @throws IndexOutOfBoundsException if start is negative or greater than {@code + * sequence.length()} + */ + public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + Preconditions.checkPositionIndex(start, length); + for (int i = start; i < length; i++) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the last matching character in a character sequence, or {@code -1} if no + * matching character is present. + * + *

The default implementation iterates over the sequence in reverse order calling {@link + * #matches} for each character. + * + * @param sequence the character sequence to examine from the end + * @return an index, or {@code -1} if no character matches + */ + public int lastIndexIn(CharSequence sequence) { + for (int i = sequence.length() - 1; i >= 0; i--) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the number of matching characters found in a character sequence. + */ + public int countIn(CharSequence sequence) { + int count = 0; + for (int i = 0; i < sequence.length(); i++) { + if (matches(sequence.charAt(i))) { + count++; + } + } + return count; + } + + /** + * Returns a string containing all non-matching characters of a character sequence, in order. For + * example:

   {@code
+   *
+   *   CharMatcher.is('a').removeFrom("bazaar")}
+ * + * ... returns {@code "bzr"}. + */ + @CheckReturnValue + public String removeFrom(CharSequence sequence) { + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + + char[] chars = string.toCharArray(); + int spread = 1; + + // This unusual loop comes from extensive benchmarking + OUT: while (true) { + pos++; + while (true) { + if (pos == chars.length) { + break OUT; + } + if (matches(chars[pos])) { + break; + } + chars[pos - spread] = chars[pos]; + pos++; + } + spread++; + } + return new String(chars, 0, pos - spread); + } + + /** + * Returns a string containing all matching characters of a character sequence, in order. For + * example:
   {@code
+   *
+   *   CharMatcher.is('a').retainFrom("bazaar")}
+ * + * ... returns {@code "aaa"}. + */ + @CheckReturnValue + public String retainFrom(CharSequence sequence) { + return negate().removeFrom(sequence); + } + + /** + * Returns a string copy of the input character sequence, with each character that matches this + * matcher replaced by a given replacement character. For example:
   {@code
+   *
+   *   CharMatcher.is('a').replaceFrom("radar", 'o')}
+ * + * ... returns {@code "rodor"}. + * + *

The default implementation uses {@link #indexIn(CharSequence)} to find the first matching + * character, then iterates the remainder of the sequence calling {@link #matches(char)} for each + * character. + * + * @param sequence the character sequence to replace matching characters in + * @param replacement the character to append to the result string in place of each matching + * character in {@code sequence} + * @return the new string + */ + @CheckReturnValue + public String replaceFrom(CharSequence sequence, char replacement) { + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + char[] chars = string.toCharArray(); + chars[pos] = replacement; + for (int i = pos + 1; i < chars.length; i++) { + if (matches(chars[i])) { + chars[i] = replacement; + } + } + return new String(chars); + } + + /** + * Returns a string copy of the input character sequence, with each character that matches this + * matcher replaced by a given replacement sequence. For example:

   {@code
+   *
+   *   CharMatcher.is('a').replaceFrom("yaha", "oo")}
+ * + * ... returns {@code "yoohoo"}. + * + *

Note: If the replacement is a fixed string with only one character, you are better + * off calling {@link #replaceFrom(CharSequence, char)} directly. + * + * @param sequence the character sequence to replace matching characters in + * @param replacement the characters to append to the result string in place of each matching + * character in {@code sequence} + * @return the new string + */ + @CheckReturnValue + public String replaceFrom(CharSequence sequence, CharSequence replacement) { + int replacementLen = replacement.length(); + if (replacementLen == 0) { + return removeFrom(sequence); + } + if (replacementLen == 1) { + return replaceFrom(sequence, replacement.charAt(0)); + } + + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + + int len = string.length(); + StringBuilder buf = new StringBuilder((len * 3 / 2) + 16); + + int oldpos = 0; + do { + buf.append(string, oldpos, pos); + buf.append(replacement); + oldpos = pos + 1; + pos = indexIn(string, oldpos); + } while (pos != -1); + + buf.append(string, oldpos, len); + return buf.toString(); + } + + /** + * Returns a substring of the input character sequence that omits all characters this matcher + * matches from the beginning and from the end of the string. For example:

   {@code
+   *
+   *   CharMatcher.anyOf("ab").trimFrom("abacatbab")}
+ * + * ... returns {@code "cat"}. + * + *

Note that:

   {@code
+   *
+   *   CharMatcher.inRange('\0', ' ').trimFrom(str)}
+ * + * ... is equivalent to {@link String#trim()}. + */ + @CheckReturnValue + public String trimFrom(CharSequence sequence) { + int len = sequence.length(); + int first; + int last; + + for (first = 0; first < len; first++) { + if (!matches(sequence.charAt(first))) { + break; + } + } + for (last = len - 1; last > first; last--) { + if (!matches(sequence.charAt(last))) { + break; + } + } + + return sequence.subSequence(first, last + 1).toString(); + } + + /** + * Returns a substring of the input character sequence that omits all characters this matcher + * matches from the beginning of the string. For example:
 {@code
+   *
+   *   CharMatcher.anyOf("ab").trimLeadingFrom("abacatbab")}
+ * + * ... returns {@code "catbab"}. + */ + @CheckReturnValue + public String trimLeadingFrom(CharSequence sequence) { + int len = sequence.length(); + int first; + + for (first = 0; first < len; first++) { + if (!matches(sequence.charAt(first))) { + break; + } + } + + return sequence.subSequence(first, len).toString(); + } + + /** + * Returns a substring of the input character sequence that omits all characters this matcher + * matches from the end of the string. For example:
 {@code
+   *
+   *   CharMatcher.anyOf("ab").trimTrailingFrom("abacatbab")}
+ * + * ... returns {@code "abacat"}. + */ + @CheckReturnValue + public String trimTrailingFrom(CharSequence sequence) { + int len = sequence.length(); + int last; + + for (last = len - 1; last >= 0; last--) { + if (!matches(sequence.charAt(last))) { + break; + } + } + + return sequence.subSequence(0, last + 1).toString(); + } + + /** + * Returns a string copy of the input character sequence, with each group of consecutive + * characters that match this matcher replaced by a single replacement character. For example: + *
   {@code
+   *
+   *   CharMatcher.anyOf("eko").collapseFrom("bookkeeper", '-')}
+ * + * ... returns {@code "b-p-r"}. + * + *

The default implementation uses {@link #indexIn(CharSequence)} to find the first matching + * character, then iterates the remainder of the sequence calling {@link #matches(char)} for each + * character. + * + * @param sequence the character sequence to replace matching groups of characters in + * @param replacement the character to append to the result string in place of each group of + * matching characters in {@code sequence} + * @return the new string + */ + @CheckReturnValue + public String collapseFrom(CharSequence sequence, char replacement) { + int first = indexIn(sequence); + if (first == -1) { + return sequence.toString(); + } + + // TODO(kevinb): see if this implementation can be made faster + StringBuilder builder = new StringBuilder(sequence.length()) + .append(sequence.subSequence(0, first)) + .append(replacement); + boolean in = true; + for (int i = first + 1; i < sequence.length(); i++) { + char c = sequence.charAt(i); + if (matches(c)) { + if (!in) { + builder.append(replacement); + in = true; + } + } else { + builder.append(c); + in = false; + } + } + return builder.toString(); + } + + /** + * Collapses groups of matching characters exactly as {@link #collapseFrom} does, except that + * groups of matching characters at the start or end of the sequence are removed without + * replacement. + */ + @CheckReturnValue + public String trimAndCollapseFrom(CharSequence sequence, char replacement) { + int first = negate().indexIn(sequence); + if (first == -1) { + return ""; // everything matches. nothing's left. + } + StringBuilder builder = new StringBuilder(sequence.length()); + boolean inMatchingGroup = false; + for (int i = first; i < sequence.length(); i++) { + char c = sequence.charAt(i); + if (matches(c)) { + inMatchingGroup = true; + } else { + if (inMatchingGroup) { + builder.append(replacement); + inMatchingGroup = false; + } + builder.append(c); + } + } + return builder.toString(); + } + + // Predicate interface + + /** + * Returns {@code true} if this matcher matches the given character. + * + * @throws NullPointerException if {@code character} is null + */ + @Override public boolean apply(Character character) { + return matches(character); + } + + /** + * Returns a string representation of this {@code CharMatcher}, such as + * {@code CharMatcher.or(WHITESPACE, JAVA_DIGIT)}. + */ + @Override + public String toString() { + return description; + } + + /** + * Determines whether a character is whitespace according to the latest Unicode standard, as + * illustrated + * here. + * This is not the same definition used by other Java APIs. (See a + * comparison of several + * definitions of "whitespace".) + * + *

Note: as the Unicode definition evolves, we will modify this constant to keep it up + * to date. + */ + public static final CharMatcher WHITESPACE = new CharMatcher("CharMatcher.WHITESPACE") { + /** + * A special-case CharMatcher for Unicode whitespace characters that is extremely + * efficient both in space required and in time to check for matches. + * + * Implementation details. + * It turns out that all current (early 2012) Unicode characters are unique modulo 79: + * so we can construct a lookup table of exactly 79 entries, and just check the character code + * mod 79, and see if that character is in the table. + * + * There is a 1 at the beginning of the table so that the null character is not listed + * as whitespace. + * + * Other things we tried that did not prove to be beneficial, mostly due to speed concerns: + * + * * Binary search into the sorted list of characters, i.e., what + * CharMatcher.anyOf() does + * * Perfect hash function into a table of size 26 (using an offset table and a special + * Jenkins hash function) + * * Perfect-ish hash function that required two lookups into a single table of size 26. + * * Using a power-of-2 sized hash table (size 64) with linear probing. + * + * --Christopher Swenson, February 2012. + */ + + // Mod-79 lookup table. + private final char[] table = {1, 0, 160, 0, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 0, + 8232, 8233, 0, 0, 0, 0, 0, 8239, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 12288, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, + 8200, 8201, 8202, 0, 0, 0, 0, 0, 8287, 5760, 0, 0, 6158, 0, 0, 0}; + + @Override public boolean matches(char c) { + return table[c % 79] == c; + } + + @Override public CharMatcher precomputed() { + return this; + } + }; +} diff --git a/guava/src/com/google/common/base/Charsets.java b/guava/src/com/google/common/base/Charsets.java new file mode 100644 index 0000000..79c9128 --- /dev/null +++ b/guava/src/com/google/common/base/Charsets.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.nio.charset.Charset; + +/** + * Contains constant definitions for the six standard {@link Charset} instances, which are + * guaranteed to be supported by all Java platform implementations. + * + *

Assuming you're free to choose, note that {@link #UTF_8} is widely preferred. + * + *

See the Guava User Guide article on + * {@code Charsets}. + * + * @author Mike Bostock + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Charsets { + private Charsets() {} + + /** + * US-ASCII: seven-bit ASCII, the Basic Latin block of the Unicode character set (ISO646-US). + */ + @GwtIncompatible("Non-UTF-8 Charset") + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + /** + * ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1). + */ + @GwtIncompatible("Non-UTF-8 Charset") + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** + * UTF-8: eight-bit UCS Transformation Format. + */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * UTF-16BE: sixteen-bit UCS Transformation Format, big-endian byte order. + */ + @GwtIncompatible("Non-UTF-8 Charset") + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + + /** + * UTF-16LE: sixteen-bit UCS Transformation Format, little-endian byte order. + */ + @GwtIncompatible("Non-UTF-8 Charset") + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + + /** + * UTF-16: sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order + * mark. + */ + @GwtIncompatible("Non-UTF-8 Charset") + public static final Charset UTF_16 = Charset.forName("UTF-16"); + + /* + * Please do not add new Charset references to this class, unless those character encodings are + * part of the set required to be supported by all Java platform implementations! Any Charsets + * initialized here may cause unexpected delays when this class is loaded. See the Charset + * Javadocs for the list of built-in character encodings. + */ +} diff --git a/guava/src/com/google/common/base/Defaults.java b/guava/src/com/google/common/base/Defaults.java new file mode 100644 index 0000000..b3e8555 --- /dev/null +++ b/guava/src/com/google/common/base/Defaults.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * This class provides default values for all Java types, as defined by the JLS. + * + * @author Ben Yu + * @since 1.0 + */ +public final class Defaults { + private Defaults() {} + + private static final Map, Object> DEFAULTS; + + static { + Map, Object> map = new HashMap, Object>(); + put(map, boolean.class, false); + put(map, char.class, '\0'); + put(map, byte.class, (byte) 0); + put(map, short.class, (short) 0); + put(map, int.class, 0); + put(map, long.class, 0L); + put(map, float.class, 0f); + put(map, double.class, 0d); + DEFAULTS = Collections.unmodifiableMap(map); + } + + private static void put(Map, Object> map, Class type, T value) { + map.put(type, value); + } + + /** + * Returns the default value of {@code type} as defined by JLS --- {@code 0} for numbers, {@code + * false} for {@code boolean} and {@code '\0'} for {@code char}. For non-primitive types and + * {@code void}, null is returned. + */ + @SuppressWarnings("unchecked") + public static T defaultValue(Class type) { + return (T) DEFAULTS.get(type); + } +} diff --git a/guava/src/com/google/common/base/Enums.java b/guava/src/com/google/common/base/Enums.java new file mode 100644 index 0000000..6105410 --- /dev/null +++ b/guava/src/com/google/common/base/Enums.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.lang.reflect.Field; + +import javax.annotation.Nullable; + +/** + * Utility methods for working with {@link Enum} instances. + * + * @author Steve McKay + * + * @since 9.0 + */ +@GwtCompatible(emulated = true) +@Beta +public final class Enums { + + private Enums() {} + + /** + * Returns the {@link Field} in which {@code enumValue} is defined. + * For example, to get the {@code Description} annotation on the {@code GOLF} + * constant of enum {@code Sport}, use + * {@code Enums.getField(Sport.GOLF).getAnnotation(Description.class)}. + * + * @since 12.0 + */ + @GwtIncompatible("reflection") + public static Field getField(Enum enumValue) { + Class clazz = enumValue.getDeclaringClass(); + try { + return clazz.getDeclaredField(enumValue.name()); + } catch (NoSuchFieldException impossible) { + throw new AssertionError(impossible); + } + } + + /** + * Returns a {@link Function} that maps an {@link Enum} name to the associated + * {@code Enum} constant. The {@code Function} will return {@code null} if the + * {@code Enum} constant does not exist. + * + * @param enumClass the {@link Class} of the {@code Enum} declaring the + * constant values. + */ + public static > Function valueOfFunction(Class enumClass) { + return new ValueOfFunction(enumClass); + } + + /** + * A {@link Function} that maps an {@link Enum} name to the associated + * constant, or {@code null} if the constant does not exist. + */ + private static final class ValueOfFunction> + implements Function, Serializable { + + private final Class enumClass; + + private ValueOfFunction(Class enumClass) { + this.enumClass = checkNotNull(enumClass); + } + + @Override + public T apply(String value) { + try { + return Enum.valueOf(enumClass, value); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override public boolean equals(@Nullable Object obj) { + return obj instanceof ValueOfFunction && + enumClass.equals(((ValueOfFunction) obj).enumClass); + } + + @Override public int hashCode() { + return enumClass.hashCode(); + } + + @Override public String toString() { + return "Enums.valueOf(" + enumClass + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an optional enum constant for the given type, using {@link Enum#valueOf}. If the + * constant does not exist, {@link Optional#absent} is returned. A common use case is for parsing + * user input or falling back to a default enum constant. For example, + * {@code Enums.getIfPresent(Country.class, countryInput).or(Country.DEFAULT);} + * + * @since 12.0 + */ + public static > Optional getIfPresent(Class enumClass, String value) { + checkNotNull(enumClass); + checkNotNull(value); + try { + return Optional.of(Enum.valueOf(enumClass, value)); + } catch (IllegalArgumentException iae) { + return Optional.absent(); + } + } +} diff --git a/guava/src/com/google/common/base/Equivalence.java b/guava/src/com/google/common/base/Equivalence.java new file mode 100644 index 0000000..d039283 --- /dev/null +++ b/guava/src/com/google/common/base/Equivalence.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * A strategy for determining whether two instances are considered equivalent. Examples of + * equivalences are the {@link Equivalences#identity() identity equivalence} and {@link + * Equivalences#equals equals equivalence}. + * + * @author Bob Lee + * @author Ben Yu + * @author Gregory Kick + * @since 10.0 (mostly source-compatible since 4.0) + */ +@GwtCompatible +public abstract class Equivalence { + /** + * Constructor for use by subclasses. + */ + protected Equivalence() {} + + /** + * Returns {@code true} if the given objects are considered equivalent. + * + *

The {@code equivalent} method implements an equivalence relation on object references: + * + *

    + *
  • It is reflexive: for any reference {@code x}, including null, {@code + * equivalent(x, x)} returns {@code true}. + *
  • It is symmetric: for any references {@code x} and {@code y}, {@code + * equivalent(x, y) == equivalent(y, x)}. + *
  • It is transitive: for any references {@code x}, {@code y}, and {@code z}, if + * {@code equivalent(x, y)} returns {@code true} and {@code equivalent(y, z)} returns {@code + * true}, then {@code equivalent(x, z)} returns {@code true}. + *
  • It is consistent: for any references {@code x} and {@code y}, multiple invocations + * of {@code equivalent(x, y)} consistently return {@code true} or consistently return {@code + * false} (provided that neither {@code x} nor {@code y} is modified). + *
+ */ + public final boolean equivalent(@Nullable T a, @Nullable T b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return doEquivalent(a, b); + } + + /** + * Returns {@code true} if {@code a} and {@code b} are considered equivalent. + * + *

Called by {@link #equivalent}. {@code a} and {@code b} are not the same + * object and are not nulls. + * + * @since 10.0 (previously, subclasses would override equivalent()) + */ + protected abstract boolean doEquivalent(T a, T b); + + /** + * Returns a hash code for {@code t}. + * + *

The {@code hash} has the following properties: + *

    + *
  • It is consistent: for any reference {@code x}, multiple invocations of + * {@code hash(x}} consistently return the same value provided {@code x} remains unchanged + * according to the definition of the equivalence. The hash need not remain consistent from + * one execution of an application to another execution of the same application. + *
  • It is distributable accross equivalence: for any references {@code x} and {@code y}, + * if {@code equivalent(x, y)}, then {@code hash(x) == hash(y)}. It is not necessary + * that the hash be distributable accorss inequivalence. If {@code equivalence(x, y)} + * is false, {@code hash(x) == hash(y)} may still be true. + *
  • {@code hash(null)} is {@code 0}. + *
+ */ + public final int hash(@Nullable T t) { + if (t == null) { + return 0; + } + return doHash(t); + } + + /** + * Returns a hash code for non-null object {@code t}. + * + *

Called by {@link #hash}. + * + * @since 10.0 (previously, subclasses would override hash()) + */ + protected abstract int doHash(T t); + + /** + * Returns a new equivalence relation for {@code F} which evaluates equivalence by first applying + * {@code function} to the argument, then evaluating using {@code this}. That is, for any pair of + * non-null objects {@code x} and {@code y}, {@code + * equivalence.onResultOf(function).equivalent(a, b)} is true if and only if {@code + * equivalence.equivalent(function.apply(a), function.apply(b))} is true. + * + *

For example:

   {@code
+   *
+   *    Equivalence SAME_AGE = Equivalences.equals().onResultOf(GET_PERSON_AGE);
+   * }
+ * + *

{@code function} will never be invoked with a null value. + * + *

Note that {@code function} must be consistent according to {@code this} equivalence + * relation. That is, invoking {@link Function#apply} multiple times for a given value must return + * equivalent results. + * For example, {@code Equivalences.identity().onResultOf(Functions.toStringFunction())} is broken + * because it's not guaranteed that {@link Object#toString}) always returns the same string + * instance. + * + * @since 10.0 + */ + public final Equivalence onResultOf(Function function) { + return new FunctionalEquivalence(function, this); + } + + /** + * Returns a wrapper of {@code reference} that implements + * {@link Wrapper#equals(Object) Object.equals()} such that + * {@code wrap(this, a).equals(wrap(this, b))} if and only if {@code this.equivalent(a, b)}. + * + * @since 10.0 + */ + public final Wrapper wrap(@Nullable S reference) { + return new Wrapper(this, reference); + } + + /** + * Wraps an object so that {@link #equals(Object)} and {@link #hashCode()} delegate to an + * {@link Equivalence}. + * + *

For example, given an {@link Equivalence} for {@link String strings} named {@code equiv} + * that tests equivalence using their lengths: + * + *

   {@code
+   *   equiv.wrap("a").equals(equiv.wrap("b")) // true
+   *   equiv.wrap("a").equals(equiv.wrap("hello")) // false
+   * }
+ * + *

Note in particular that an equivalence wrapper is never equal to the object it wraps. + * + *

   {@code
+   *   equiv.wrap(obj).equals(obj) // always false
+   * }
+ * + * @since 10.0 + */ + public static final class Wrapper implements Serializable { + private final Equivalence equivalence; + @Nullable private final T reference; + + private Wrapper(Equivalence equivalence, @Nullable T reference) { + this.equivalence = checkNotNull(equivalence); + this.reference = reference; + } + + /** Returns the (possibly null) reference wrapped by this instance. */ + @Nullable public T get() { + return reference; + } + + /** + * Returns {@code true} if {@link Equivalence#equivalent(Object, Object)} applied to the wrapped + * references is {@code true} and both wrappers use the {@link Object#equals(Object) same} + * equivalence. + */ + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Wrapper) { + Wrapper that = (Wrapper) obj; + /* + * We cast to Equivalence here because we can't check the type of the reference held + * by the other wrapper. But, by checking that the Equivalences are equal, we know that + * whatever type it is, it is assignable to the type handled by this wrapper's equivalence. + */ + @SuppressWarnings("unchecked") + Equivalence equivalence = (Equivalence) this.equivalence; + return equivalence.equals(that.equivalence) + && equivalence.equivalent(this.reference, that.reference); + } else { + return false; + } + } + + /** + * Returns the result of {@link Equivalence#hash(Object)} applied to the the wrapped reference. + */ + @Override public int hashCode() { + return equivalence.hash(reference); + } + + /** + * Returns a string representation for this equivalence wrapper. The form of this string + * representation is not specified. + */ + @Override public String toString() { + return equivalence + ".wrap(" + reference + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an equivalence over iterables based on the equivalence of their elements. More + * specifically, two iterables are considered equivalent if they both contain the same number of + * elements, and each pair of corresponding elements is equivalent according to + * {@code this}. Null iterables are equivalent to one another. + * + *

Note that this method performs a similar function for equivalences as {@link + * com.google.common.collect.Ordering#lexicographical} does for orderings. + * + * @since 10.0 + */ + @GwtCompatible(serializable = true) + public final Equivalence> pairwise() { + // Ideally, the returned equivalence would support Iterable. However, + // the need for this is so rare that it's not worth making callers deal with the ugly wildcard. + return new PairwiseEquivalence(this); + } + + /** + * Returns a predicate that evaluates to true if and only if the input is + * equivalent to {@code target} according to this equivalence relation. + * + * @since 10.0 + */ + @Beta + public final Predicate equivalentTo(@Nullable T target) { + return new EquivalentToPredicate(this, target); + } + + private static final class EquivalentToPredicate implements Predicate, Serializable { + + private final Equivalence equivalence; + @Nullable private final T target; + + EquivalentToPredicate(Equivalence equivalence, @Nullable T target) { + this.equivalence = checkNotNull(equivalence); + this.target = target; + } + + @Override public boolean apply(@Nullable T input) { + return equivalence.equivalent(input, target); + } + + @Override public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof EquivalentToPredicate) { + EquivalentToPredicate that = (EquivalentToPredicate) obj; + return equivalence.equals(that.equivalence) + && Objects.equal(target, that.target); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(equivalence, target); + } + + @Override public String toString() { + return equivalence + ".equivalentTo(" + target + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an equivalence that delegates to {@link Object#equals} and {@link Object#hashCode}. + * {@link Equivalence#equivalent} returns {@code true} if both values are null, or if neither + * value is null and {@link Object#equals} returns {@code true}. {@link Equivalence#hash} returns + * {@code 0} if passed a null value. + * + * @since 13.0 + * @since 8.0 (in Equivalences with null-friendly behavior) + * @since 4.0 (in Equivalences) + */ + public static Equivalence equals() { + return Equals.INSTANCE; + } + + /** + * Returns an equivalence that uses {@code ==} to compare values and {@link + * System#identityHashCode(Object)} to compute the hash code. {@link Equivalence#equivalent} + * returns {@code true} if {@code a == b}, including in the case that a and b are both null. + * + * @since 13.0 + * @since 4.0 (in Equivalences) + */ + public static Equivalence identity() { + return Identity.INSTANCE; + } + + static final class Equals extends Equivalence + implements Serializable { + + static final Equals INSTANCE = new Equals(); + + @Override protected boolean doEquivalent(Object a, Object b) { + return a.equals(b); + } + @Override public int doHash(Object o) { + return o.hashCode(); + } + + private Object readResolve() { + return INSTANCE; + } + private static final long serialVersionUID = 1; + } + + static final class Identity extends Equivalence + implements Serializable { + + static final Identity INSTANCE = new Identity(); + + @Override protected boolean doEquivalent(Object a, Object b) { + return false; + } + + @Override protected int doHash(Object o) { + return System.identityHashCode(o); + } + + private Object readResolve() { + return INSTANCE; + } + private static final long serialVersionUID = 1; + } +} diff --git a/guava/src/com/google/common/base/Equivalences.java b/guava/src/com/google/common/base/Equivalences.java new file mode 100644 index 0000000..23f947c --- /dev/null +++ b/guava/src/com/google/common/base/Equivalences.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Contains static factory methods for creating {@code Equivalence} instances. + * + *

All methods return serializable instances. + * + * @author Bob Lee + * @author Kurt Alfred Kluever + * @author Gregory Kick + * @since 4.0 + */ +@Beta +@GwtCompatible +public final class Equivalences { + private Equivalences() {} + + /** + * Returns an equivalence that delegates to {@link Object#equals} and {@link Object#hashCode}. + * {@link Equivalence#equivalent} returns {@code true} if both values are null, or if neither + * value is null and {@link Object#equals} returns {@code true}. {@link Equivalence#hash} returns + * {@code 0} if passed a null value. + * + * @since 8.0 (present null-friendly behavior) + * @since 4.0 (otherwise) + * @deprecated This method has been moved to {@link Equivalence#equals}. This method is scheduled + * to be removed in Guava release 14.0. + */ + @Deprecated + public static Equivalence equals() { + return Equivalence.Equals.INSTANCE; + } + + /** + * Returns an equivalence that uses {@code ==} to compare values and {@link + * System#identityHashCode(Object)} to compute the hash code. {@link Equivalence#equivalent} + * returns {@code true} if {@code a == b}, including in the case that a and b are both null. + * + * @deprecated This method has been moved to {@link Equivalence#identity}. This method is schedule + * to be removed in Guava release 14.0. + */ + @Deprecated + public static Equivalence identity() { + return Equivalence.Identity.INSTANCE; + } +} diff --git a/guava/src/com/google/common/base/FinalizablePhantomReference.java b/guava/src/com/google/common/base/FinalizablePhantomReference.java new file mode 100644 index 0000000..c694bc9 --- /dev/null +++ b/guava/src/com/google/common/base/FinalizablePhantomReference.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +/** + * Phantom reference with a {@code finalizeReferent()} method which a background thread invokes + * after the garbage collector reclaims the referent. This is a simpler alternative to using a + * {@link ReferenceQueue}. + * + *

Unlike a normal phantom reference, this reference will be cleared automatically. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +public abstract class FinalizablePhantomReference extends PhantomReference + implements FinalizableReference { + /** + * Constructs a new finalizable phantom reference. + * + * @param referent to phantom reference + * @param queue that should finalize the referent + */ + protected FinalizablePhantomReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/guava/src/com/google/common/base/FinalizableReference.java b/guava/src/com/google/common/base/FinalizableReference.java new file mode 100644 index 0000000..ab2dec3 --- /dev/null +++ b/guava/src/com/google/common/base/FinalizableReference.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * Implemented by references that have code to run after garbage collection of their referents. + * + * @see FinalizableReferenceQueue + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +public interface FinalizableReference { + /** + * Invoked on a background thread after the referent has been garbage collected unless security + * restrictions prevented starting a background thread, in which case this method is invoked when + * new references are created. + */ + void finalizeReferent(); +} diff --git a/guava/src/com/google/common/base/FinalizableReferenceQueue.java b/guava/src/com/google/common/base/FinalizableReferenceQueue.java new file mode 100644 index 0000000..aaa5f76 --- /dev/null +++ b/guava/src/com/google/common/base/FinalizableReferenceQueue.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.VisibleForTesting; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A reference queue with an associated background thread that dequeues references and invokes + * {@link FinalizableReference#finalizeReferent()} on them. + * + *

Keep a strong reference to this object until all of the associated referents have been + * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code + * finalizeReferent()} on the remaining references. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +public class FinalizableReferenceQueue { + /* + * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a + * map built by MapMaker) no longer has a strong reference to this object, the garbage collector + * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the + * Finalizer to stop. + * + * If this library is loaded in the system class loader, FinalizableReferenceQueue can load + * Finalizer directly with no problems. + * + * If this library is loaded in an application class loader, it's important that Finalizer not + * have a strong reference back to the class loader. Otherwise, you could have a graph like this: + * + * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader + * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance + * + * Even if no other references to classes from the application class loader remain, the Finalizer + * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the + * Finalizer running, and as a result, the application class loader can never be reclaimed. + * + * This means that dynamically loaded web applications and OSGi bundles can't be unloaded. + * + * If the library is loaded in an application class loader, we try to break the cycle by loading + * Finalizer in its own independent class loader: + * + * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue + * -> etc. -> Decoupled class loader -> Finalizer + * + * Now, Finalizer no longer keeps an indirect strong reference to the static + * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed + * at which point the Finalizer thread will stop and its decoupled class loader can also be + * reclaimed. + * + * If any of this fails along the way, we fall back to loading Finalizer directly in the + * application class loader. + */ + + private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer"; + + /** Reference to Finalizer.startFinalizer(). */ + private static final Method startFinalizer; + static { + Class finalizer = loadFinalizer( + new SystemLoader(), new DecoupledLoader(), new DirectLoader()); + startFinalizer = getStartFinalizer(finalizer); + } + + /** + * The actual reference queue that our background thread will poll. + */ + final ReferenceQueue queue; + + /** + * Whether or not the background thread started successfully. + */ + final boolean threadStarted; + + /** + * Constructs a new queue. + */ + @SuppressWarnings("unchecked") + public FinalizableReferenceQueue() { + // We could start the finalizer lazily, but I'd rather it blow up early. + ReferenceQueue queue; + boolean threadStarted = false; + try { + queue = (ReferenceQueue) + startFinalizer.invoke(null, FinalizableReference.class, this); + threadStarted = true; + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); // startFinalizer() is public + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to start reference finalizer thread." + + " Reference cleanup will only occur when new references are created.", t); + queue = new ReferenceQueue(); + } + + this.queue = queue; + this.threadStarted = threadStarted; + } + + /** + * Repeatedly dequeues references from the queue and invokes {@link + * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a + * no-op if the background thread was created successfully. + */ + void cleanUp() { + if (threadStarted) { + return; + } + + Reference reference; + while ((reference = queue.poll()) != null) { + /* + * This is for the benefit of phantom references. Weak and soft references will have already + * been cleared by this point. + */ + reference.clear(); + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + } + } + + /** + * Iterates through the given loaders until it finds one that can load Finalizer. + * + * @return Finalizer.class + */ + private static Class loadFinalizer(FinalizerLoader... loaders) { + for (FinalizerLoader loader : loaders) { + Class finalizer = loader.loadFinalizer(); + if (finalizer != null) { + return finalizer; + } + } + + throw new AssertionError(); + } + + /** + * Loads Finalizer.class. + */ + interface FinalizerLoader { + + /** + * Returns Finalizer.class or null if this loader shouldn't or can't load it. + * + * @throws SecurityException if we don't have the appropriate privileges + */ + Class loadFinalizer(); + } + + /** + * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path, + * we needn't create a separate loader. + */ + static class SystemLoader implements FinalizerLoader { + // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable + // finding Finalizer on the system class path even if it is there. + @VisibleForTesting + static boolean disabled; + + @Override + public Class loadFinalizer() { + if (disabled) { + return null; + } + ClassLoader systemLoader; + try { + systemLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException e) { + logger.info("Not allowed to access system class loader."); + return null; + } + if (systemLoader != null) { + try { + return systemLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + // Ignore. Finalizer is simply in a child class loader. + return null; + } + } else { + return null; + } + } + } + + /** + * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to + * our class loader (which could be that of a dynamically loaded web application or OSGi bundle), + * it would prevent our class loader from getting garbage collected. + */ + static class DecoupledLoader implements FinalizerLoader { + private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader." + + "Loading Finalizer in the current class loader instead. As a result, you will not be able" + + "to garbage collect this class loader. To support reclaiming this class loader, either" + + "resolve the underlying issue, or move Google Collections to your system class path."; + + @Override + public Class loadFinalizer() { + try { + /* + * We use URLClassLoader because it's the only concrete class loader implementation in the + * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this + * class loader: + * + * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader + * + * System class loader will (and must) be the parent. + */ + ClassLoader finalizerLoader = newLoader(getBaseUrl()); + return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (Exception e) { + logger.log(Level.WARNING, LOADING_ERROR, e); + return null; + } + } + + /** + * Gets URL for base of path containing Finalizer.class. + */ + URL getBaseUrl() throws IOException { + // Find URL pointing to Finalizer.class file. + String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; + URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); + if (finalizerUrl == null) { + throw new FileNotFoundException(finalizerPath); + } + + // Find URL pointing to base of class path. + String urlString = finalizerUrl.toString(); + if (!urlString.endsWith(finalizerPath)) { + throw new IOException("Unsupported path style: " + urlString); + } + urlString = urlString.substring(0, urlString.length() - finalizerPath.length()); + return new URL(finalizerUrl, urlString); + } + + /** Creates a class loader with the given base URL as its classpath. */ + URLClassLoader newLoader(URL base) { + // We use the bootstrap class loader as the parent because Finalizer by design uses + // only standard Java classes. That also means that FinalizableReferenceQueueTest + // doesn't pick up the wrong version of the Finalizer class. + return new URLClassLoader(new URL[] {base}, null); + } + } + + /** + * Loads Finalizer directly using the current class loader. We won't be able to garbage collect + * this class loader, but at least the world doesn't end. + */ + static class DirectLoader implements FinalizerLoader { + @Override + public Class loadFinalizer() { + try { + return Class.forName(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + } + + /** + * Looks up Finalizer.startFinalizer(). + */ + static Method getStartFinalizer(Class finalizer) { + try { + return finalizer.getMethod("startFinalizer", Class.class, Object.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } +} diff --git a/guava/src/com/google/common/base/FinalizableSoftReference.java b/guava/src/com/google/common/base/FinalizableSoftReference.java new file mode 100644 index 0000000..88607f4 --- /dev/null +++ b/guava/src/com/google/common/base/FinalizableSoftReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@code finalizeReferent()} method which a background thread invokes after + * the garbage collector reclaims the referent. This is a simpler alternative to using a {@link + * ReferenceQueue}. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +public abstract class FinalizableSoftReference extends SoftReference + implements FinalizableReference { + /** + * Constructs a new finalizable soft reference. + * + * @param referent to softly reference + * @param queue that should finalize the referent + */ + protected FinalizableSoftReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/guava/src/com/google/common/base/FinalizableWeakReference.java b/guava/src/com/google/common/base/FinalizableWeakReference.java new file mode 100644 index 0000000..b14d023 --- /dev/null +++ b/guava/src/com/google/common/base/FinalizableWeakReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@code finalizeReferent()} method which a background thread invokes after + * the garbage collector reclaims the referent. This is a simpler alternative to using a {@link + * ReferenceQueue}. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +public abstract class FinalizableWeakReference extends WeakReference + implements FinalizableReference { + /** + * Constructs a new finalizable weak reference. + * + * @param referent to weakly reference + * @param queue that should finalize the referent + */ + protected FinalizableWeakReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/guava/src/com/google/common/base/Function.java b/guava/src/com/google/common/base/Function.java new file mode 100644 index 0000000..9c29264 --- /dev/null +++ b/guava/src/com/google/common/base/Function.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +/** + * Determines an output value based on an input value. + * + *

See the Guava User Guide article on the use of {@code + * Function}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface Function { + /** + * Returns the result of applying this function to {@code input}. This method is generally + * expected, but not absolutely required, to have the following properties: + * + *

    + *
  • Its execution does not cause any observable side effects. + *
  • The computation is consistent with equals; that is, {@link Objects#equal + * Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a), + * function.apply(b))}. + *
+ * + * @throws NullPointerException if {@code input} is null and this function does not accept null + * arguments + */ + @Nullable T apply(@Nullable F input); + + /** + * Indicates whether another object is equal to this function. + * + *

Most implementations will have no reason to override the behavior of {@link Object#equals}. + * However, an implementation may also choose to return {@code true} whenever {@code object} is a + * {@link Function} that it considers interchangeable with this one. "Interchangeable" + * typically means that {@code Objects.equal(this.apply(f), that.apply(f))} is true for all + * {@code f} of type {@code F}. Note that a {@code false} result from this method does not imply + * that the functions are known not to be interchangeable. + */ + @Override + boolean equals(@Nullable Object object); +} diff --git a/guava/src/com/google/common/base/FunctionalEquivalence.java b/guava/src/com/google/common/base/FunctionalEquivalence.java new file mode 100644 index 0000000..a5e9c13 --- /dev/null +++ b/guava/src/com/google/common/base/FunctionalEquivalence.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * Equivalence applied on functional result. + * + * @author Bob Lee + * @since 10.0 + */ +@Beta +@GwtCompatible +final class FunctionalEquivalence extends Equivalence + implements Serializable { + + private static final long serialVersionUID = 0; + + private final Function function; + private final Equivalence resultEquivalence; + + FunctionalEquivalence( + Function function, Equivalence resultEquivalence) { + this.function = checkNotNull(function); + this.resultEquivalence = checkNotNull(resultEquivalence); + } + + @Override protected boolean doEquivalent(F a, F b) { + return resultEquivalence.equivalent(function.apply(a), function.apply(b)); + } + + @Override protected int doHash(F a) { + return resultEquivalence.hash(function.apply(a)); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof FunctionalEquivalence) { + FunctionalEquivalence that = (FunctionalEquivalence) obj; + return function.equals(that.function) + && resultEquivalence.equals(that.resultEquivalence); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(function, resultEquivalence); + } + + @Override public String toString() { + return resultEquivalence + ".onResultOf(" + function + ")"; + } +} diff --git a/guava/src/com/google/common/base/Functions.java b/guava/src/com/google/common/base/Functions.java new file mode 100644 index 0000000..65fc2c6 --- /dev/null +++ b/guava/src/com/google/common/base/Functions.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@code Function} instances. + * + *

All methods return serializable functions as long as they're given serializable parameters. + * + *

See the Guava User Guide article on the use of {@code + * Function}. + * + * @author Mike Bostock + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Functions { + private Functions() {} + + /** + * Returns a function that calls {@code toString()} on its argument. The function does not accept + * nulls; it will throw a {@link NullPointerException} when applied to {@code null}. + * + *

Warning: The returned function may not be consistent with equals (as + * documented at {@link Function#apply}). For example, this function yields different results for + * the two equal instances {@code ImmutableSet.of(1, 2)} and {@code ImmutableSet.of(2, 1)}. + */ + public static Function toStringFunction() { + return ToStringFunction.INSTANCE; + } + + // enum singleton pattern + private enum ToStringFunction implements Function { + INSTANCE; + + @Override + public String apply(Object o) { + checkNotNull(o); // eager for GWT. + return o.toString(); + } + + @Override public String toString() { + return "toString"; + } + } + + /** + * Returns the identity function. + */ + @SuppressWarnings("unchecked") + public static Function identity() { + return (Function) IdentityFunction.INSTANCE; + } + + // enum singleton pattern + private enum IdentityFunction implements Function { + INSTANCE; + + @Override + public Object apply(Object o) { + return o; + } + + @Override public String toString() { + return "identity"; + } + } + + /** + * Returns a function which performs a map lookup. The returned function throws an {@link + * IllegalArgumentException} if given a key that does not exist in the map. + */ + public static Function forMap(Map map) { + return new FunctionForMapNoDefault(map); + } + + private static class FunctionForMapNoDefault implements Function, Serializable { + final Map map; + + FunctionForMapNoDefault(Map map) { + this.map = checkNotNull(map); + } + + @Override + public V apply(K key) { + V result = map.get(key); + checkArgument(result != null || map.containsKey(key), "Key '%s' not present in map", key); + return result; + } + + @Override public boolean equals(@Nullable Object o) { + if (o instanceof FunctionForMapNoDefault) { + FunctionForMapNoDefault that = (FunctionForMapNoDefault) o; + return map.equals(that.map); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + @Override public String toString() { + return "forMap(" + map + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function which performs a map lookup with a default value. The function created by + * this method returns {@code defaultValue} for all inputs that do not belong to the map's key + * set. + * + * @param map source map that determines the function behavior + * @param defaultValue the value to return for inputs that aren't map keys + * @return function that returns {@code map.get(a)} when {@code a} is a key, or {@code + * defaultValue} otherwise + */ + public static Function forMap(Map map, @Nullable V defaultValue) { + return new ForMapWithDefault(map, defaultValue); + } + + private static class ForMapWithDefault implements Function, Serializable { + final Map map; + final V defaultValue; + + ForMapWithDefault(Map map, @Nullable V defaultValue) { + this.map = checkNotNull(map); + this.defaultValue = defaultValue; + } + + @Override + public V apply(K key) { + V result = map.get(key); + return (result != null || map.containsKey(key)) ? result : defaultValue; + } + + @Override public boolean equals(@Nullable Object o) { + if (o instanceof ForMapWithDefault) { + ForMapWithDefault that = (ForMapWithDefault) o; + return map.equals(that.map) && Objects.equal(defaultValue, that.defaultValue); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(map, defaultValue); + } + + @Override public String toString() { + return "forMap(" + map + ", defaultValue=" + defaultValue + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the composition of two functions. For {@code f: A->B} and {@code g: B->C}, composition + * is defined as the function h such that {@code h(a) == g(f(a))} for each {@code a}. + * + * @param g the second function to apply + * @param f the first function to apply + * @return the composition of {@code f} and {@code g} + * @see function composition + */ + public static Function compose(Function g, Function f) { + return new FunctionComposition(g, f); + } + + private static class FunctionComposition implements Function, Serializable { + private final Function g; + private final Function f; + + public FunctionComposition(Function g, Function f) { + this.g = checkNotNull(g); + this.f = checkNotNull(f); + } + + @Override + public C apply(A a) { + return g.apply(f.apply(a)); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof FunctionComposition) { + FunctionComposition that = (FunctionComposition) obj; + return f.equals(that.f) && g.equals(that.g); + } + return false; + } + + @Override public int hashCode() { + return f.hashCode() ^ g.hashCode(); + } + + @Override public String toString() { + return g.toString() + "(" + f.toString() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns the same boolean output as the given predicate for all inputs. + * + *

The returned function is consistent with equals (as documented at {@link + * Function#apply}) if and only if {@code predicate} is itself consistent with equals. + */ + public static Function forPredicate(Predicate predicate) { + return new PredicateFunction(predicate); + } + + /** @see Functions#forPredicate */ + private static class PredicateFunction implements Function, Serializable { + private final Predicate predicate; + + private PredicateFunction(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + + @Override + public Boolean apply(T t) { + return predicate.apply(t); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof PredicateFunction) { + PredicateFunction that = (PredicateFunction) obj; + return predicate.equals(that.predicate); + } + return false; + } + + @Override public int hashCode() { + return predicate.hashCode(); + } + + @Override public String toString() { + return "forPredicate(" + predicate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns {@code value} for any input. + * + * @param value the constant value for the function to return + * @return a function that always returns {@code value} + */ + public static Function constant(@Nullable E value) { + return new ConstantFunction(value); + } + + private static class ConstantFunction implements Function, Serializable { + private final E value; + + public ConstantFunction(@Nullable E value) { + this.value = value; + } + + @Override + public E apply(@Nullable Object from) { + return value; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof ConstantFunction) { + ConstantFunction that = (ConstantFunction) obj; + return Objects.equal(value, that.value); + } + return false; + } + + @Override public int hashCode() { + return (value == null) ? 0 : value.hashCode(); + } + + @Override public String toString() { + return "constant(" + value + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function that always returns the result of invoking {@link Supplier#get} on {@code + * supplier}, regardless of its input. + * + * @since 10.0 + */ + @Beta + public static Function forSupplier(Supplier supplier) { + return new SupplierFunction(supplier); + } + + /** @see Functions#forSupplier*/ + private static class SupplierFunction implements Function, Serializable { + + private final Supplier supplier; + + private SupplierFunction(Supplier supplier) { + this.supplier = checkNotNull(supplier); + } + + @Override public T apply(@Nullable Object input) { + return supplier.get(); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof SupplierFunction) { + SupplierFunction that = (SupplierFunction) obj; + return this.supplier.equals(that.supplier); + } + return false; + } + + @Override public int hashCode() { + return supplier.hashCode(); + } + + @Override public String toString() { + return "forSupplier(" + supplier + ")"; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/base/Joiner.java b/guava/src/com/google/common/base/Joiner.java new file mode 100644 index 0000000..9391550 --- /dev/null +++ b/guava/src/com/google/common/base/Joiner.java @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.CheckReturnValue; +import javax.annotation.Nullable; + +/** + * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a + * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns + * them as a {@link String}. Example:

   {@code
+ *
+ *   Joiner joiner = Joiner.on("; ").skipNulls();
+ *    . . .
+ *   return joiner.join("Harry", null, "Ron", "Hermione");}
+ * + * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are + * converted to strings using {@link Object#toString()} before being appended. + * + *

If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining + * methods will throw {@link NullPointerException} if any given element is null. + * + *

Warning: joiner instances are always immutable; a configuration method such as {@code + * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner + * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code + * static final} constants.

   {@code
+ *
+ *   // Bad! Do not do this!
+ *   Joiner joiner = Joiner.on(',');
+ *   joiner.skipNulls(); // does nothing!
+ *   return joiner.join("wrong", null, "wrong");}
+ * + *

See the Guava User Guide article on {@code Joiner}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public class Joiner { + /** + * Returns a joiner which automatically places {@code separator} between consecutive elements. + */ + public static Joiner on(String separator) { + return new Joiner(separator); + } + + /** + * Returns a joiner which automatically places {@code separator} between consecutive elements. + */ + public static Joiner on(char separator) { + return new Joiner(String.valueOf(separator)); + } + + private final String separator; + + private Joiner(String separator) { + this.separator = checkNotNull(separator); + } + + private Joiner(Joiner prototype) { + this.separator = prototype.separator; + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #appendTo(Appendable, Iterator)} by casting {@code parts} to + * {@code Iterator}, or better yet, by implementing only {@code Iterator} and not + * {@code Iterable}. This method is scheduled for deletion in June 2013. + */ + @Beta + @Deprecated + public + final & Iterator> A + appendTo(A appendable, I parts) throws IOException { + return appendTo(appendable, (Iterator) parts); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + */ + public A appendTo(A appendable, Iterable parts) throws IOException { + return appendTo(appendable, parts.iterator()); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + * + * @since 11.0 + */ + public A appendTo(A appendable, Iterator parts) throws IOException { + checkNotNull(appendable); + if (parts.hasNext()) { + appendable.append(toString(parts.next())); + while (parts.hasNext()) { + appendable.append(separator); + appendable.append(toString(parts.next())); + } + } + return appendable; + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + */ + public final A appendTo(A appendable, Object[] parts) throws IOException { + return appendTo(appendable, Arrays.asList(parts)); + } + + /** + * Appends to {@code appendable} the string representation of each of the remaining arguments. + */ + public final A appendTo( + A appendable, @Nullable Object first, @Nullable Object second, Object... rest) + throws IOException { + return appendTo(appendable, iterable(first, second, rest)); + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #appendTo(StringBuilder, Iterator)} by casting {@code parts} to + * {@code Iterator}, or better yet, by implementing only {@code Iterator} and not + * {@code Iterable}. This method is scheduled for deletion in June 2013. + */ + @Beta + @Deprecated + public + final & Iterator> StringBuilder + appendTo(StringBuilder builder, I parts) { + return appendTo(builder, (Iterator) parts); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Iterable parts) { + return appendTo(builder, parts.iterator()); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + * + * @since 11.0 + */ + public final StringBuilder appendTo(StringBuilder builder, Iterator parts) { + try { + appendTo((Appendable) builder, parts); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { + return appendTo(builder, Arrays.asList(parts)); + } + + /** + * Appends to {@code builder} the string representation of each of the remaining arguments. + * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not + * throw {@link IOException}. + */ + public final StringBuilder appendTo( + StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) { + return appendTo(builder, iterable(first, second, rest)); + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #join(Iterator)} by casting {@code parts} to + * {@code Iterator}, or better yet, by implementing only {@code Iterator} and not + * {@code Iterable}. This method is scheduled for deletion in June 2013. + */ + @Beta + @Deprecated + public + final & Iterator> String join(I parts) { + return join((Iterator) parts); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + */ + public final String join(Iterable parts) { + return join(parts.iterator()); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + * + * @since 11.0 + */ + public final String join(Iterator parts) { + return appendTo(new StringBuilder(), parts).toString(); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + */ + public final String join(Object[] parts) { + return join(Arrays.asList(parts)); + } + + /** + * Returns a string containing the string representation of each argument, using the previously + * configured separator between each. + */ + public final String join(@Nullable Object first, @Nullable Object second, Object... rest) { + return join(iterable(first, second, rest)); + } + + /** + * Returns a joiner with the same behavior as this one, except automatically substituting {@code + * nullText} for any provided null elements. + */ + @CheckReturnValue + public Joiner useForNull(final String nullText) { + checkNotNull(nullText); + return new Joiner(this) { + @Override CharSequence toString(Object part) { + return (part == null) ? nullText : Joiner.this.toString(part); + } + + @Override public Joiner useForNull(String nullText) { + checkNotNull(nullText); // weird: just to satisfy NullPointerTester. + throw new UnsupportedOperationException("already specified useForNull"); + } + + @Override public Joiner skipNulls() { + throw new UnsupportedOperationException("already specified useForNull"); + } + }; + } + + /** + * Returns a joiner with the same behavior as this joiner, except automatically skipping over any + * provided null elements. + */ + @CheckReturnValue + public Joiner skipNulls() { + return new Joiner(this) { + @Override public A appendTo(A appendable, Iterator parts) + throws IOException { + checkNotNull(appendable, "appendable"); + checkNotNull(parts, "parts"); + while (parts.hasNext()) { + Object part = parts.next(); + if (part != null) { + appendable.append(Joiner.this.toString(part)); + break; + } + } + while (parts.hasNext()) { + Object part = parts.next(); + if (part != null) { + appendable.append(separator); + appendable.append(Joiner.this.toString(part)); + } + } + return appendable; + } + + @Override public Joiner useForNull(String nullText) { + checkNotNull(nullText); // weird: just to satisfy NullPointerTester. + throw new UnsupportedOperationException("already specified skipNulls"); + } + + @Override public MapJoiner withKeyValueSeparator(String kvs) { + checkNotNull(kvs); // weird: just to satisfy NullPointerTester. + throw new UnsupportedOperationException("can't use .skipNulls() with maps"); + } + }; + } + + /** + * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as + * this {@code Joiner} otherwise. + */ + @CheckReturnValue + public MapJoiner withKeyValueSeparator(String keyValueSeparator) { + return new MapJoiner(this, keyValueSeparator); + } + + /** + * An object that joins map entries in the same manner as {@code Joiner} joins iterables and + * arrays. Like {@code Joiner}, it is thread-safe and immutable. + * + *

In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code + * Multimap} entries in two distinct modes: + * + *

+ * + * @since 2.0 (imported from Google Collections Library) + */ + public final static class MapJoiner { + private final Joiner joiner; + private final String keyValueSeparator; + + private MapJoiner(Joiner joiner, String keyValueSeparator) { + this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull + this.keyValueSeparator = checkNotNull(keyValueSeparator); + } + + /** + * Appends the string representation of each entry of {@code map}, using the previously + * configured separator and key-value separator, to {@code appendable}. + */ + public A appendTo(A appendable, Map map) throws IOException { + return appendTo(appendable, map.entrySet()); + } + + /** + * Appends the string representation of each entry of {@code map}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. + */ + public StringBuilder appendTo(StringBuilder builder, Map map) { + return appendTo(builder, map.entrySet()); + } + + /** + * Returns a string containing the string representation of each entry of {@code map}, using the + * previously configured separator and key-value separator. + */ + public String join(Map map) { + return join(map.entrySet()); + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #appendTo(Appendable, Iterator)} by casting {@code entries} to + * {@code Iterator>}, or better yet, by implementing only + * {@code Iterator} and not {@code Iterable}. This method is scheduled for deletion + * in June 2013. + */ + @Beta + @Deprecated + public + > & Iterator>> + A appendTo(A appendable, I entries) throws IOException { + Iterator> iterator = entries; + return appendTo(appendable, iterator); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code appendable}. + * + * @since 10.0 + */ + @Beta + public A appendTo(A appendable, Iterable> entries) + throws IOException { + return appendTo(appendable, entries.iterator()); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code appendable}. + * + * @since 11.0 + */ + @Beta + public A appendTo(A appendable, Iterator> parts) + throws IOException { + checkNotNull(appendable); + if (parts.hasNext()) { + Entry entry = parts.next(); + appendable.append(joiner.toString(entry.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(entry.getValue())); + while (parts.hasNext()) { + appendable.append(joiner.separator); + Entry e = parts.next(); + appendable.append(joiner.toString(e.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(e.getValue())); + } + } + return appendable; + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #appendTo(StringBuilder, Iterator)} by casting {@code entries} to + * {@code Iterator>}, or better yet, by implementing only + * {@code Iterator} and not {@code Iterable}. This method is scheduled for deletion + * in June 2013. + */ + @Beta + @Deprecated + public + > & Iterator>> + StringBuilder appendTo(StringBuilder builder, I entries) throws IOException { + Iterator> iterator = entries; + return appendTo(builder, iterator); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. + * + * @since 10.0 + */ + @Beta + public StringBuilder appendTo(StringBuilder builder, Iterable> entries) { + return appendTo(builder, entries.iterator()); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. + * + * @since 11.0 + */ + @Beta + public StringBuilder appendTo(StringBuilder builder, Iterator> entries) { + try { + appendTo((Appendable) builder, entries); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Deprecated. + * + * @since 11.0 + * @deprecated use {@link #join(Iterator)} by casting {@code entries} to + * {@code Iterator>}, or better yet, by implementing only + * {@code Iterator} and not {@code Iterable}. This method is scheduled for deletion + * in June 2013. + */ + @Beta + @Deprecated + public + > & Iterator>> + String join(I entries) throws IOException { + Iterator> iterator = entries; + return join(iterator); + } + + /** + * Returns a string containing the string representation of each entry in {@code entries}, using + * the previously configured separator and key-value separator. + * + * @since 10.0 + */ + @Beta + public String join(Iterable> entries) { + return join(entries.iterator()); + } + + /** + * Returns a string containing the string representation of each entry in {@code entries}, using + * the previously configured separator and key-value separator. + * + * @since 11.0 + */ + @Beta + public String join(Iterator> entries) { + return appendTo(new StringBuilder(), entries).toString(); + } + + /** + * Returns a map joiner with the same behavior as this one, except automatically substituting + * {@code nullText} for any provided null keys or values. + */ + @CheckReturnValue + public MapJoiner useForNull(String nullText) { + return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); + } + } + + CharSequence toString(Object part) { + checkNotNull(part); // checkNotNull for GWT (do not optimize). + return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); + } + + private static Iterable iterable( + final Object first, final Object second, final Object[] rest) { + checkNotNull(rest); + return new AbstractList() { + @Override public int size() { + return rest.length + 2; + } + + @Override public Object get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + return rest[index - 2]; + } + } + }; + } +} diff --git a/guava/src/com/google/common/base/MediumCharMatcher.java b/guava/src/com/google/common/base/MediumCharMatcher.java new file mode 100644 index 0000000..f55ad5d --- /dev/null +++ b/guava/src/com/google/common/base/MediumCharMatcher.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +/** + * An immutable version of CharMatcher for medium-sized sets of characters that uses a hash table + * with linear probing to check for matches. + * + * @author Christopher Swenson + */ +@GwtCompatible +final class MediumCharMatcher extends CharMatcher { + static final int MAX_SIZE = 1023; + private final char[] table; + private final boolean containsZero; + private final long filter; + + private MediumCharMatcher(char[] table, long filter, boolean containsZero, + String description) { + super(description); + this.table = table; + this.filter = filter; + this.containsZero = containsZero; + } + + private boolean checkFilter(int c) { + return 1 == (1 & (filter >> c)); + } + + // This is all essentially copied from ImmutableSet, but we have to duplicate because + // of dependencies. + + // Represents how tightly we can pack things, as a maximum. + private static final double DESIRED_LOAD_FACTOR = 0.5; + + /** + * Returns an array size suitable for the backing array of a hash table that + * uses open addressing with linear probing in its implementation. The + * returned size is the smallest power of two that can hold setSize elements + * with the desired load factor. + */ + @VisibleForTesting static int chooseTableSize(int setSize) { + if (setSize == 1) { + return 2; + } + // Correct the size for open addressing to match desired load factor. + // Round up to the next highest power of 2. + int tableSize = Integer.highestOneBit(setSize - 1) << 1; + while (tableSize * DESIRED_LOAD_FACTOR < setSize) { + tableSize <<= 1; + } + return tableSize; + } + + // This method is thread-safe, since if any two threads execute it simultaneously, all + // that will happen is that they compute the same data structure twice, but nothing will ever + // be incorrect. + @Override + public CharMatcher precomputed() { + return this; + } + + static CharMatcher from(char[] chars, String description) { + // Compute the filter. + long filter = 0; + int size = chars.length; + boolean containsZero = (chars[0] == 0); + // Compute the filter. + for (char c : chars) { + filter |= 1L << c; + } + // Compute the hash table. + char[] table = new char[chooseTableSize(size)]; + int mask = table.length - 1; + for (char c : chars) { + int index = c & mask; + while (true) { + // Check for empty. + if (table[index] == 0) { + table[index] = c; + break; + } + // Linear probing. + index = (index + 1) & mask; + } + } + return new MediumCharMatcher(table, filter, containsZero, description); + } + + @Override + public boolean matches(char c) { + if (c == 0) { + return containsZero; + } + if (!checkFilter(c)) { + return false; + } + int mask = table.length - 1; + int startingIndex = c & mask; + int index = startingIndex; + do { + // Check for empty. + if (table[index] == 0) { + return false; + // Check for match. + } else if (table[index] == c) { + return true; + } else { + // Linear probing. + index = (index + 1) & mask; + } + // Check to see if we wrapped around the whole table. + } while (index != startingIndex); + return false; + } +} diff --git a/guava/src/com/google/common/base/Objects.java b/guava/src/com/google/common/base/Objects.java new file mode 100644 index 0000000..e1c79a3 --- /dev/null +++ b/guava/src/com/google/common/base/Objects.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Helper functions that can operate on any {@code Object}. + * + *

See the Guava User Guide on writing + * {@code Object} methods with {@code Objects}. + * + * @author Laurence Gonsalves + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Objects { + private Objects() {} + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + *

    + *
  • {@code true} if {@code a} and {@code b} are both null. + *
  • {@code true} if {@code a} and {@code b} are both non-null and they are + * equal according to {@link Object#equals(Object)}. + *
  • {@code false} in all other situations. + *
+ * + *

This assumes that any non-null objects passed to this function conform + * to the {@code equals()} contract. + */ + public static boolean equal(@Nullable Object a, @Nullable Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Generates a hash code for multiple values. The hash code is generated by + * calling {@link Arrays#hashCode(Object[])}. + * + *

This is useful for implementing {@link Object#hashCode()}. For example, + * in an object that has three properties, {@code x}, {@code y}, and + * {@code z}, one could write: + *

+   * public int hashCode() {
+   *   return Objects.hashCode(getX(), getY(), getZ());
+   * }
+ * + * Warning: When a single object is supplied, the returned hash code + * does not equal the hash code of that object. + */ + public static int hashCode(@Nullable Object... objects) { + return Arrays.hashCode(objects); + } + + /** + * Creates an instance of {@link ToStringHelper}. + * + *

This is helpful for implementing {@link Object#toString()}. + * Specification by example:

   {@code
+   *   // Returns "ClassName{}"
+   *   Objects.toStringHelper(this)
+   *       .toString();
+   *
+   *   // Returns "ClassName{x=1}"
+   *   Objects.toStringHelper(this)
+   *       .add("x", 1)
+   *       .toString();
+   *
+   *   // Returns "MyObject{x=1}"
+   *   Objects.toStringHelper("MyObject")
+   *       .add("x", 1)
+   *       .toString();
+   *
+   *   // Returns "ClassName{x=1, y=foo}"
+   *   Objects.toStringHelper(this)
+   *       .add("x", 1)
+   *       .add("y", "foo")
+   *       .toString();
+   *   }}
+   *
+   *   // Returns "ClassName{x=1}"
+   *   Objects.toStringHelper(this)
+   *       .omitNullValues()
+   *       .add("x", 1)
+   *       .add("y", null)
+   *       .toString();
+   *   }}
+ * + *

Note that in GWT, class names are often obfuscated. + * + * @param self the object to generate the string for (typically {@code this}), + * used only for its class name + * @since 2.0 + */ + public static ToStringHelper toStringHelper(Object self) { + return new ToStringHelper(simpleName(self.getClass())); + } + + /** + * Creates an instance of {@link ToStringHelper} in the same manner as + * {@link Objects#toStringHelper(Object)}, but using the name of {@code clazz} + * instead of using an instance's {@link Object#getClass()}. + * + *

Note that in GWT, class names are often obfuscated. + * + * @param clazz the {@link Class} of the instance + * @since 7.0 (source-compatible since 2.0) + */ + public static ToStringHelper toStringHelper(Class clazz) { + return new ToStringHelper(simpleName(clazz)); + } + + /** + * Creates an instance of {@link ToStringHelper} in the same manner as + * {@link Objects#toStringHelper(Object)}, but using {@code className} instead + * of using an instance's {@link Object#getClass()}. + * + * @param className the name of the instance type + * @since 7.0 (source-compatible since 2.0) + */ + public static ToStringHelper toStringHelper(String className) { + return new ToStringHelper(className); + } + + /** + * {@link Class#getSimpleName()} is not GWT compatible yet, so we + * provide our own implementation. + */ + private static String simpleName(Class clazz) { + String name = clazz.getName(); + + // the nth anonymous class has a class name ending in "Outer$n" + // and local inner classes have names ending in "Outer.$1Inner" + name = name.replaceAll("\\$[0-9]+", "\\$"); + + // we want the name of the inner class all by its lonesome + int start = name.lastIndexOf('$'); + + // if this isn't an inner class, just find the start of the + // top level class name. + if (start == -1) { + start = name.lastIndexOf('.'); + } + return name.substring(start + 1); + } + + /** + * Returns the first of two given parameters that is not {@code null}, if + * either is, or otherwise throws a {@link NullPointerException}. + * + *

Note: if {@code first} is represented as an {@code Optional}, + * this can be accomplished with {@code first.or(second)}. That approach also + * allows for lazy evaluation of the fallback instance, using + * {@code first.or(Supplier)}. + * + * @return {@code first} if {@code first} is not {@code null}, or + * {@code second} if {@code first} is {@code null} and {@code second} is + * not {@code null} + * @throws NullPointerException if both {@code first} and {@code second} were + * {@code null} + * @since 3.0 + */ + public static T firstNonNull(@Nullable T first, @Nullable T second) { + return first != null ? first : checkNotNull(second); + } + + /** + * Support class for {@link Objects#toStringHelper}. + * + * @author Jason Lee + * @since 2.0 + */ + public static final class ToStringHelper { + private final String className; + private final List valueHolders = + new LinkedList(); + private boolean omitNullValues = false; + + /** + * Use {@link Objects#toStringHelper(Object)} to create an instance. + */ + private ToStringHelper(String className) { + this.className = checkNotNull(className); + } + + /** + * When called, the formatted output returned by {@link #toString()} will + * ignore {@code null} values. + * + * @since 12.0 + */ + @Beta + public ToStringHelper omitNullValues() { + omitNullValues = true; + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. If {@code value} is {@code null}, the string {@code "null"} + * is used, unless {@link #omitNullValues()} is called, in which case this + * name/value pair will not be added. + */ + public ToStringHelper add(String name, @Nullable Object value) { + checkNotNull(name); + addHolder(value).builder.append(name).append('=').append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, boolean value) { + checkNameAndAppend(name).append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, char value) { + checkNameAndAppend(name).append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, double value) { + checkNameAndAppend(name).append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, float value) { + checkNameAndAppend(name).append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, int value) { + checkNameAndAppend(name).append(value); + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} + * format. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper add(String name, long value) { + checkNameAndAppend(name).append(value); + return this; + } + + private StringBuilder checkNameAndAppend(String name) { + checkNotNull(name); + return addHolder().builder.append(name).append('='); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, Object)} instead + * and give value a readable name. + */ + public ToStringHelper addValue(@Nullable Object value) { + addHolder(value).builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, boolean)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(boolean value) { + addHolder().builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, char)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(char value) { + addHolder().builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, double)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(double value) { + addHolder().builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, float)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(float value) { + addHolder().builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, int)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(int value) { + addHolder().builder.append(value); + return this; + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, long)} instead + * and give value a readable name. + * + * @since 11.0 (source-compatible since 2.0) + */ + public ToStringHelper addValue(long value) { + addHolder().builder.append(value); + return this; + } + + /** + * Returns a string in the format specified by {@link + * Objects#toStringHelper(Object)}. + */ + @Override public String toString() { + // create a copy to keep it consistent in case value changes + boolean omitNullValuesSnapshot = omitNullValues; + boolean needsSeparator = false; + StringBuilder builder = new StringBuilder(32).append(className) + .append('{'); + for (ValueHolder valueHolder : valueHolders) { + if (!omitNullValuesSnapshot || !valueHolder.isNull) { + if (needsSeparator) { + builder.append(", "); + } else { + needsSeparator = true; + } + // must explicitly cast it, otherwise GWT tests might fail because + // it tries to access StringBuilder.append(StringBuilder), which is + // a private method + // TODO(user): change once 5904010 is fixed + CharSequence sequence = valueHolder.builder; + builder.append(sequence); + } + } + return builder.append('}').toString(); + } + + private ValueHolder addHolder() { + ValueHolder valueHolder = new ValueHolder(); + valueHolders.add(valueHolder); + return valueHolder; + } + + private ValueHolder addHolder(@Nullable Object value) { + ValueHolder valueHolder = addHolder(); + valueHolder.isNull = (value == null); + return valueHolder; + } + + private static final class ValueHolder { + final StringBuilder builder = new StringBuilder(); + boolean isNull; + } + } +} diff --git a/guava/src/com/google/common/base/Optional.java b/guava/src/com/google/common/base/Optional.java new file mode 100644 index 0000000..8cd7cda --- /dev/null +++ b/guava/src/com/google/common/base/Optional.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An immutable object that may contain a non-null reference to another object. Each + * instance of this type either contains a non-null reference, or contains nothing (in + * which case we say that the reference is "absent"); it is never said to "contain {@code + * null}". + * + *

A non-null {@code Optional} reference can be used as a replacement for a nullable + * {@code T} reference. It allows you to represent "a {@code T} that must be present" and + * a "a {@code T} that might be absent" as two distinct types in your program, which can + * aid clarity. + * + *

Some uses of this class include + * + *

    + *
  • As a method return type, as an alternative to returning {@code null} to indicate + * that no value was available + *
  • To distinguish between "unknown" (for example, not present in a map) and "known to + * have no value" (present in the map, with value {@code Optional.absent()}) + *
  • To wrap nullable references for storage in a collection that does not support + * {@code null} (though there are + * + * several other approaches to this that should be considered first) + *
+ * + *

A common alternative to using this class is to find or create a suitable + * null object for the + * type in question. + * + *

This class is not intended as a direct analogue of any existing "option" or "maybe" + * construct from other programming environments, though it may bear some similarities. + * + *

See the Guava User Guide article on + * using {@code Optional}. + * + * @param the type of instance that can be contained. {@code Optional} is naturally + * covariant on this type, so it is safe to cast an {@code Optional} to {@code + * Optional} for any supertype {@code S} of {@code T}. + * @author Kurt Alfred Kluever + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible(serializable = true) +public abstract class Optional implements Serializable { + /** + * Returns an {@code Optional} instance with no contained reference. + */ + @SuppressWarnings("unchecked") + public static Optional absent() { + return (Optional) Absent.INSTANCE; + } + + /** + * Returns an {@code Optional} instance containing the given non-null reference. + */ + public static Optional of(T reference) { + return new Present(checkNotNull(reference)); + } + + /** + * If {@code nullableReference} is non-null, returns an {@code Optional} instance containing that + * reference; otherwise returns {@link Optional#absent}. + */ + public static Optional fromNullable(@Nullable T nullableReference) { + return (nullableReference == null) + ? Optional.absent() + : new Present(nullableReference); + } + + Optional() {} + + /** + * Returns {@code true} if this holder contains a (non-null) instance. + */ + public abstract boolean isPresent(); + + /** + * Returns the contained instance, which must be present. If the instance might be + * absent, use {@link #or(Object)} or {@link #orNull} instead. + * + * @throws IllegalStateException if the instance is absent ({@link #isPresent} returns + * {@code false}) + */ + public abstract T get(); + + /** + * Returns the contained instance if it is present; {@code defaultValue} otherwise. If + * no default value should be required because the instance is known to be present, use + * {@link #get()} instead. For a default value of {@code null}, use {@link #orNull}. + * + *

Note about generics: The signature {@code public T or(T defaultValue)} is overly + * restrictive. However, the ideal signature, {@code public S or(S)}, is not legal + * Java. As a result, some sensible operations involving subtypes are compile errors: + *

   {@code
+   *
+   *   Optional optionalInt = getSomeOptionalInt();
+   *   Number value = optionalInt.or(0.5); // error
+   *
+   *   FluentIterable numbers = getSomeNumbers();
+   *   Optional first = numbers.first();
+   *   Number value = first.or(0.5); // error}
+ * + * As a workaround, it is always safe to cast an {@code Optional} to {@code + * Optional}. Casting either of the above example {@code Optional} instances to {@code + * Optional} (where {@code Number} is the desired output type) solves the problem: + *
   {@code
+   *
+   *   Optional optionalInt = (Optional) getSomeOptionalInt();
+   *   Number value = optionalInt.or(0.5); // fine
+   *
+   *   FluentIterable numbers = getSomeNumbers();
+   *   Optional first = (Optional) numbers.first();
+   *   Number value = first.or(0.5); // fine}
+ */ + public abstract T or(T defaultValue); + + /** + * Returns this {@code Optional} if it has a value present; {@code secondChoice} + * otherwise. + */ + @Beta + public abstract Optional or(Optional secondChoice); + + /** + * Returns the contained instance if it is present; {@code supplier.get()} otherwise. If the + * supplier returns {@code null}, a {@link NullPointerException} is thrown. + * + * @throws NullPointerException if the supplier returns {@code null} + */ + @Beta + public abstract T or(Supplier supplier); + + /** + * Returns the contained instance if it is present; {@code null} otherwise. If the + * instance is known to be present, use {@link #get()} instead. + */ + @Nullable + public abstract T orNull(); + + /** + * Returns an immutable singleton {@link Set} whose only element is the contained instance + * if it is present; an empty immutable {@link Set} otherwise. + * + * @since 11.0 + */ + public abstract Set asSet(); + + /** + * If the instance is present, it is transformed with the given {@link Function}; otherwise, + * {@link Optional#absent} is returned. If the function returns {@code null}, a + * {@link NullPointerException} is thrown. + * + * @throws NullPointerException if the function returns {@code null} + * + * @since 12.0 + */ + @Beta + public abstract Optional transform(Function function); + + /** + * Returns {@code true} if {@code object} is an {@code Optional} instance, and either + * the contained references are {@linkplain Object#equals equal} to each other or both + * are absent. Note that {@code Optional} instances of differing parameterized types can + * be equal. + */ + @Override + public abstract boolean equals(@Nullable Object object); + + /** + * Returns a hash code for this instance. + */ + @Override + public abstract int hashCode(); + + /** + * Returns a string representation for this instance. The form of this string + * representation is unspecified. + */ + @Override + public abstract String toString(); + + /** + * Returns the value of each present instance from the supplied {@code optionals}, in order, + * skipping over occurrences of {@link Optional#absent}. Iterators are unmodifiable and are + * evaluated lazily. + * + * @since 11.0 (generics widened in 13.0) + */ + @Beta + public static Iterable presentInstances( + final Iterable> optionals) { + checkNotNull(optionals); + return new Iterable() { + @Override + public Iterator iterator() { + return new AbstractIterator() { + private final Iterator> iterator = + checkNotNull(optionals.iterator()); + + @Override + protected T computeNext() { + while (iterator.hasNext()) { + Optional optional = iterator.next(); + if (optional.isPresent()) { + return optional.get(); + } + } + return endOfData(); + } + }; + }; + }; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/base/PairwiseEquivalence.java b/guava/src/com/google/common/base/PairwiseEquivalence.java new file mode 100644 index 0000000..23ab539 --- /dev/null +++ b/guava/src/com/google/common/base/PairwiseEquivalence.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Iterator; + +import javax.annotation.Nullable; + +@GwtCompatible(serializable = true) +final class PairwiseEquivalence extends Equivalence> + implements Serializable { + + final Equivalence elementEquivalence; + + PairwiseEquivalence(Equivalence elementEquivalence) { + this.elementEquivalence = Preconditions.checkNotNull(elementEquivalence); + } + + @Override + protected boolean doEquivalent(Iterable iterableA, Iterable iterableB) { + Iterator iteratorA = iterableA.iterator(); + Iterator iteratorB = iterableB.iterator(); + + while (iteratorA.hasNext() && iteratorB.hasNext()) { + if (!elementEquivalence.equivalent(iteratorA.next(), iteratorB.next())) { + return false; + } + } + + return !iteratorA.hasNext() && !iteratorB.hasNext(); + } + + @Override + protected int doHash(Iterable iterable) { + int hash = 78721; + for (T element : iterable) { + hash = hash * 24943 + elementEquivalence.hash(element); + } + return hash; + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof PairwiseEquivalence) { + PairwiseEquivalence that = (PairwiseEquivalence) object; + return this.elementEquivalence.equals(that.elementEquivalence); + } + + return false; + } + + @Override + public int hashCode() { + return elementEquivalence.hashCode() ^ 0x46a3eb07; + } + + @Override + public String toString() { + return elementEquivalence + ".pairwise()"; + } + + private static final long serialVersionUID = 1; +} diff --git a/guava/src/com/google/common/base/Platform.java b/guava/src/com/google/common/base/Platform.java new file mode 100644 index 0000000..dcbe06c --- /dev/null +++ b/guava/src/com/google/common/base/Platform.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Jesse Wilson + */ +@GwtCompatible(emulated = true) +final class Platform { + private Platform() {} + + /** Returns a thread-local 1024-char array. */ + static char[] charBufferFromThreadLocal() { + return DEST_TL.get(); + } + + /** Calls {@link System#nanoTime()}. */ + static long systemNanoTime() { + return System.nanoTime(); + } + + /** + * A thread-local destination buffer to keep us from creating new buffers. + * The starting size is 1024 characters. If we grow past this we don't + * put it back in the threadlocal, we just keep going and grow as needed. + */ + private static final ThreadLocal DEST_TL = new ThreadLocal() { + @Override + protected char[] initialValue() { + return new char[1024]; + } + }; + + static CharMatcher precomputeCharMatcher(CharMatcher matcher) { + return matcher.precomputedInternal(); + } +} diff --git a/guava/src/com/google/common/base/Preconditions.java b/guava/src/com/google/common/base/Preconditions.java new file mode 100644 index 0000000..802a309 --- /dev/null +++ b/guava/src/com/google/common/base/Preconditions.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. This allows constructs such as + *
+ *     if (count <= 0) {
+ *       throw new IllegalArgumentException("must be positive: " + count);
+ *     }
+ * + * to be replaced with the more compact + *
+ *     checkArgument(count > 0, "must be positive: %s", count);
+ * + * Note that the sense of the expression is inverted; with {@code Preconditions} + * you declare what you expect to be true, just as you do with an + * + * {@code assert} or a JUnit {@code assertTrue} call. + * + *

Warning: only the {@code "%s"} specifier is recognized as a + * placeholder in these messages, not the full range of {@link + * String#format(String, Object[])} specifiers. + * + *

Take care not to confuse precondition checking with other similar types + * of checks! Precondition exceptions -- including those provided here, but also + * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link + * UnsupportedOperationException} and others -- are used to signal that the + * calling method has made an error. This tells the caller that it should + * not have invoked the method when it did, with the arguments it did, or + * perhaps ever. Postcondition or other invariant failures should not throw + * these types of exceptions. + * + *

See the Guava User Guide on + * using {@code Preconditions}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Preconditions { + private Preconditions() {} + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument( + boolean expression, @Nullable Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkArgument(boolean expression, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState( + boolean expression, @Nullable Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkState(boolean expression, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, @Nullable Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate + * String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning + * methods are much slower. This is a big deal - we're talking factors of + * 2-8 in microbenchmarks, not just 10-20%. (This is a hotspot optimizer + * bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. + * There is a RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, + * depending on the args, so it appears that this pattern is not directly + * applicable. But we can use the ridiculous, devious trick of throwing an + * exception in the middle of the construction of another exception. + * Hotspot is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex( + int index, int size, @Nullable String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + return index; + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index >= size + return format("%s (%s) must be less than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkPositionIndex(int index, int size) { + return checkPositionIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkPositionIndex( + int index, int size, @Nullable String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return format("%s (%s) must not be greater than size (%s)", + desc, index, size); + } + } + + /** + * Ensures that {@code start} and {@code end} specify a valid positions + * in an array, list or string of size {@code size}, and are in order. A + * position index may range from zero to {@code size}, inclusive. + * + * @param start a user-supplied index identifying a starting position in an + * array, list or string + * @param end a user-supplied index identifying a ending position in an array, + * list or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if either index is negative or is + * greater than {@code size}, or if {@code end} is less than {@code start} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return format("end index (%s) must not be less than start index (%s)", + end, start); + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments will + * be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + @VisibleForTesting static String format(String template, + @Nullable Object... args) { + template = String.valueOf(template); // null -> "null" + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder( + template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } +} diff --git a/guava/src/com/google/common/base/Predicate.java b/guava/src/com/google/common/base/Predicate.java new file mode 100644 index 0000000..89a8c36 --- /dev/null +++ b/guava/src/com/google/common/base/Predicate.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +/** + * Determines a true or false value for a given input. + * + *

See the Guava User Guide article on the use of {@code + * Predicate}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface Predicate { + /** + * Returns the result of applying this predicate to {@code input}. This method is generally + * expected, but not absolutely required, to have the following properties: + * + *

    + *
  • Its execution does not cause any observable side effects. + *
  • The computation is consistent with equals; that is, {@link Objects#equal + * Objects.equal}{@code (a, b)} implies that {@code predicate.apply(a) == + * predicate.apply(b))}. + *
+ * + * @throws NullPointerException if {@code input} is null and this predicate does not accept null + * arguments + */ + boolean apply(@Nullable T input); + + /** + * Indicates whether another object is equal to this predicate. + * + *

Most implementations will have no reason to override the behavior of {@link Object#equals}. + * However, an implementation may also choose to return {@code true} whenever {@code object} is a + * {@link Predicate} that it considers interchangeable with this one. "Interchangeable" + * typically means that {@code this.apply(t) == that.apply(t)} for all {@code t} of type + * {@code T}). Note that a {@code false} result from this method does not imply that the + * predicates are known not to be interchangeable. + */ + @Override + boolean equals(@Nullable Object object); +} diff --git a/guava/src/com/google/common/base/Predicates.java b/guava/src/com/google/common/base/Predicates.java new file mode 100644 index 0000000..5c42c55 --- /dev/null +++ b/guava/src/com/google/common/base/Predicates.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@code Predicate} instances. + * + *

All methods returns serializable predicates as long as they're given + * serializable parameters. + * + *

See the Guava User Guide article on the + * use of {@code Predicate}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Predicates { + private Predicates() {} + + // TODO(kevinb): considering having these implement a VisitablePredicate + // interface which specifies an accept(PredicateVisitor) method. + + /** + * Returns a predicate that always evaluates to {@code true}. + */ + @GwtCompatible(serializable = true) + public static Predicate alwaysTrue() { + return ObjectPredicate.ALWAYS_TRUE.withNarrowedType(); + } + + /** + * Returns a predicate that always evaluates to {@code false}. + */ + @GwtCompatible(serializable = true) + public static Predicate alwaysFalse() { + return ObjectPredicate.ALWAYS_FALSE.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is null. + */ + @GwtCompatible(serializable = true) + public static Predicate isNull() { + return ObjectPredicate.IS_NULL.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is not null. + */ + @GwtCompatible(serializable = true) + public static Predicate notNull() { + return ObjectPredicate.NOT_NULL.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the given predicate + * evaluates to {@code false}. + */ + public static Predicate not(Predicate predicate) { + return new NotPredicate(predicate); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It defensively copies the iterable passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * true}. + */ + public static Predicate and( + Iterable> components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It defensively copies the array passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * true}. + */ + public static Predicate and(Predicate... components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if both of its + * components evaluate to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. + */ + public static Predicate and(Predicate first, + Predicate second) { + return new AndPredicate(Predicates.asList( + checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a + * true predicate is found. It defensively copies the iterable passed in, so + * future changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * false}. + */ + public static Predicate or( + Iterable> components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a + * true predicate is found. It defensively copies the array passed in, so + * future changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * false}. + */ + public static Predicate or(Predicate... components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if either of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a + * true predicate is found. + */ + public static Predicate or( + Predicate first, Predicate second) { + return new OrPredicate(Predicates.asList( + checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested {@code equals()} the given target or both are null. + */ + public static Predicate equalTo(@Nullable T target) { + return (target == null) + ? Predicates.isNull() + : new IsEqualToPredicate(target); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested is an instance of the given class. If the object being tested + * is {@code null} this predicate evaluates to {@code false}. + * + *

If you want to filter an {@code Iterable} to narrow its type, consider + * using {@link com.google.common.collect.Iterables#filter(Iterable, Class)} + * in preference. + * + *

Warning: contrary to the typical assumptions about predicates (as + * documented at {@link Predicate#apply}), the returned predicate may not be + * consistent with equals. For example, {@code + * instanceOf(ArrayList.class)} will yield different results for the two equal + * instances {@code Lists.newArrayList(1)} and {@code Arrays.asList(1)}. + */ + @GwtIncompatible("Class.isInstance") + public static Predicate instanceOf(Class clazz) { + return new InstanceOfPredicate(clazz); + } + + /** + * Returns a predicate that evaluates to {@code true} if the class being + * tested is assignable from the given class. The returned predicate + * does not allow null inputs. + * + * @since 10.0 + */ + @GwtIncompatible("Class.isAssignableFrom") + @Beta + public static Predicate> assignableFrom(Class clazz) { + return new AssignableFromPredicate(clazz); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is a member of the given collection. It does not defensively + * copy the collection passed in, so future changes to it will alter the + * behavior of the predicate. + * + *

This method can technically accept any {@code Collection}, but using + * a typed collection helps prevent bugs. This approach doesn't block any + * potential users since it is always possible to use {@code + * Predicates.in()}. + * + * @param target the collection that may contain the function input + */ + public static Predicate in(Collection target) { + return new InPredicate(target); + } + + /** + * Returns the composition of a function and a predicate. For every {@code x}, + * the generated predicate returns {@code predicate(function(x))}. + * + * @return the composition of the provided function and predicate + */ + public static Predicate compose( + Predicate predicate, Function function) { + return new CompositionPredicate(predicate, function); + } + + /** + * Returns a predicate that evaluates to {@code true} if the + * {@code CharSequence} being tested contains any match for the given + * regular expression pattern. The test used is equivalent to + * {@code Pattern.compile(pattern).matcher(arg).find()} + * + * @throws java.util.regex.PatternSyntaxException if the pattern is invalid + * @since 3.0 + */ + @GwtIncompatible(value = "java.util.regex.Pattern") + public static Predicate containsPattern(String pattern) { + return new ContainsPatternPredicate(pattern); + } + + /** + * Returns a predicate that evaluates to {@code true} if the + * {@code CharSequence} being tested contains any match for the given + * regular expression pattern. The test used is equivalent to + * {@code pattern.matcher(arg).find()} + * + * @since 3.0 + */ + @GwtIncompatible(value = "java.util.regex.Pattern") + public static Predicate contains(Pattern pattern) { + return new ContainsPatternPredicate(pattern); + } + + // End public API, begin private implementation classes. + + // Package private for GWT serialization. + enum ObjectPredicate implements Predicate { + ALWAYS_TRUE { + @Override public boolean apply(@Nullable Object o) { + return true; + } + }, + ALWAYS_FALSE { + @Override public boolean apply(@Nullable Object o) { + return false; + } + }, + IS_NULL { + @Override public boolean apply(@Nullable Object o) { + return o == null; + } + }, + NOT_NULL { + @Override public boolean apply(@Nullable Object o) { + return o != null; + } + }; + + @SuppressWarnings("unchecked") // these Object predicates work for any T + Predicate withNarrowedType() { + return (Predicate) this; + } + } + + /** @see Predicates#not(Predicate) */ + private static class NotPredicate implements Predicate, Serializable { + final Predicate predicate; + + NotPredicate(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + @Override + public boolean apply(T t) { + return !predicate.apply(t); + } + @Override public int hashCode() { + return ~predicate.hashCode(); + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof NotPredicate) { + NotPredicate that = (NotPredicate) obj; + return predicate.equals(that.predicate); + } + return false; + } + @Override public String toString() { + return "Not(" + predicate.toString() + ")"; + } + private static final long serialVersionUID = 0; + } + + private static final Joiner COMMA_JOINER = Joiner.on(","); + + /** @see Predicates#and(Iterable) */ + private static class AndPredicate implements Predicate, Serializable { + private final List> components; + + private AndPredicate(List> components) { + this.components = components; + } + @Override + public boolean apply(T t) { + // Avoid using the Iterator to avoid generating garbage (issue 820). + for (int i = 0; i < components.size(); i++) { + if (!components.get(i).apply(t)) { + return false; + } + } + return true; + } + @Override public int hashCode() { + // add a random number to avoid collisions with OrPredicate + return components.hashCode() + 0x12472c2c; + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof AndPredicate) { + AndPredicate that = (AndPredicate) obj; + return components.equals(that.components); + } + return false; + } + @Override public String toString() { + return "And(" + COMMA_JOINER.join(components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#or(Iterable) */ + private static class OrPredicate implements Predicate, Serializable { + private final List> components; + + private OrPredicate(List> components) { + this.components = components; + } + @Override + public boolean apply(T t) { + // Avoid using the Iterator to avoid generating garbage (issue 820). + for (int i = 0; i < components.size(); i++) { + if (components.get(i).apply(t)) { + return true; + } + } + return false; + } + @Override public int hashCode() { + // add a random number to avoid collisions with AndPredicate + return components.hashCode() + 0x053c91cf; + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof OrPredicate) { + OrPredicate that = (OrPredicate) obj; + return components.equals(that.components); + } + return false; + } + @Override public String toString() { + return "Or(" + COMMA_JOINER.join(components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#equalTo(Object) */ + private static class IsEqualToPredicate + implements Predicate, Serializable { + private final T target; + + private IsEqualToPredicate(T target) { + this.target = target; + } + @Override + public boolean apply(T t) { + return target.equals(t); + } + @Override public int hashCode() { + return target.hashCode(); + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof IsEqualToPredicate) { + IsEqualToPredicate that = (IsEqualToPredicate) obj; + return target.equals(that.target); + } + return false; + } + @Override public String toString() { + return "IsEqualTo(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#instanceOf(Class) */ + @GwtIncompatible("Class.isInstance") + private static class InstanceOfPredicate + implements Predicate, Serializable { + private final Class clazz; + + private InstanceOfPredicate(Class clazz) { + this.clazz = checkNotNull(clazz); + } + @Override + public boolean apply(@Nullable Object o) { + return clazz.isInstance(o); + } + @Override public int hashCode() { + return clazz.hashCode(); + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof InstanceOfPredicate) { + InstanceOfPredicate that = (InstanceOfPredicate) obj; + return clazz == that.clazz; + } + return false; + } + @Override public String toString() { + return "IsInstanceOf(" + clazz.getName() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#assignableFrom(Class) */ + @GwtIncompatible("Class.isAssignableFrom") + private static class AssignableFromPredicate + implements Predicate>, Serializable { + private final Class clazz; + + private AssignableFromPredicate(Class clazz) { + this.clazz = checkNotNull(clazz); + } + @Override + public boolean apply(Class input) { + return clazz.isAssignableFrom(input); + } + @Override public int hashCode() { + return clazz.hashCode(); + } + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof AssignableFromPredicate) { + AssignableFromPredicate that = (AssignableFromPredicate) obj; + return clazz == that.clazz; + } + return false; + } + @Override public String toString() { + return "IsAssignableFrom(" + clazz.getName() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#in(Collection) */ + private static class InPredicate implements Predicate, Serializable { + private final Collection target; + + private InPredicate(Collection target) { + this.target = checkNotNull(target); + } + + @Override + public boolean apply(T t) { + try { + return target.contains(t); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof InPredicate) { + InPredicate that = (InPredicate) obj; + return target.equals(that.target); + } + return false; + } + + @Override public int hashCode() { + return target.hashCode(); + } + + @Override public String toString() { + return "In(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#compose(Predicate, Function) */ + private static class CompositionPredicate + implements Predicate, Serializable { + final Predicate p; + final Function f; + + private CompositionPredicate(Predicate p, Function f) { + this.p = checkNotNull(p); + this.f = checkNotNull(f); + } + + @Override + public boolean apply(A a) { + return p.apply(f.apply(a)); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof CompositionPredicate) { + CompositionPredicate that = (CompositionPredicate) obj; + return f.equals(that.f) && p.equals(that.p); + } + return false; + } + + @Override public int hashCode() { + return f.hashCode() ^ p.hashCode(); + } + + @Override public String toString() { + return p.toString() + "(" + f.toString() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * @see Predicates#contains(Pattern) + * @see Predicates#containsPattern(String) + */ + @GwtIncompatible("Only used by other GWT-incompatible code.") + private static class ContainsPatternPredicate + implements Predicate, Serializable { + final Pattern pattern; + + ContainsPatternPredicate(Pattern pattern) { + this.pattern = checkNotNull(pattern); + } + + ContainsPatternPredicate(String patternStr) { + this(Pattern.compile(patternStr)); + } + + @Override + public boolean apply(CharSequence t) { + return pattern.matcher(t).find(); + } + + @Override public int hashCode() { + // Pattern uses Object.hashCode, so we have to reach + // inside to build a hashCode consistent with equals. + + return Objects.hashCode(pattern.pattern(), pattern.flags()); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof ContainsPatternPredicate) { + ContainsPatternPredicate that = (ContainsPatternPredicate) obj; + + // Pattern uses Object (identity) equality, so we have to reach + // inside to compare individual fields. + return Objects.equal(pattern.pattern(), that.pattern.pattern()) + && Objects.equal(pattern.flags(), that.pattern.flags()); + } + return false; + } + + @Override public String toString() { + return Objects.toStringHelper(this) + .add("pattern", pattern) + .add("pattern.flags", Integer.toHexString(pattern.flags())) + .toString(); + } + + private static final long serialVersionUID = 0; + } + + @SuppressWarnings("unchecked") + private static List> asList( + Predicate first, Predicate second) { + return Arrays.>asList(first, second); + } + + private static List defensiveCopy(T... array) { + return defensiveCopy(Arrays.asList(array)); + } + + static List defensiveCopy(Iterable iterable) { + ArrayList list = new ArrayList(); + for (T element : iterable) { + list.add(checkNotNull(element)); + } + return list; + } +} diff --git a/guava/src/com/google/common/base/Present.java b/guava/src/com/google/common/base/Present.java new file mode 100644 index 0000000..aa1ddc5 --- /dev/null +++ b/guava/src/com/google/common/base/Present.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collections; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of an {@link Optional} containing a reference. + */ +@GwtCompatible +final class Present extends Optional { + private final T reference; + + Present(T reference) { + this.reference = reference; + } + + @Override public boolean isPresent() { + return true; + } + + @Override public T get() { + return reference; + } + + @Override public T or(T defaultValue) { + checkNotNull(defaultValue, "use orNull() instead of or(null)"); + return reference; + } + + @Override public Optional or(Optional secondChoice) { + checkNotNull(secondChoice); + return this; + } + + @Override public T or(Supplier supplier) { + checkNotNull(supplier); + return reference; + } + + @Override public T orNull() { + return reference; + } + + @Override public Set asSet() { + return Collections.singleton(reference); + } + + @Override public Optional transform(Function function) { + return new Present(checkNotNull(function.apply(reference), + "Transformation function cannot return null.")); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Present) { + Present other = (Present) object; + return reference.equals(other.reference); + } + return false; + } + + @Override public int hashCode() { + return 0x598df91c + reference.hashCode(); + } + + @Override public String toString() { + return "Optional.of(" + reference + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/base/SmallCharMatcher.java b/guava/src/com/google/common/base/SmallCharMatcher.java new file mode 100644 index 0000000..b62a488 --- /dev/null +++ b/guava/src/com/google/common/base/SmallCharMatcher.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +/** + * An immutable small version of CharMatcher that uses an efficient hash table implementation, with + * non-power-of-2 sizing to try to use no reprobing, if possible. + * + * @author Christopher Swenson + */ +@GwtCompatible +final class SmallCharMatcher extends CharMatcher { + static final int MAX_SIZE = 63; + static final int MAX_TABLE_SIZE = 128; + private final boolean reprobe; + private final char[] table; + private final boolean containsZero; + final long filter; + + private SmallCharMatcher(char[] table, long filter, boolean containsZero, + boolean reprobe, String description) { + super(description); + this.table = table; + this.filter = filter; + this.containsZero = containsZero; + this.reprobe = reprobe; + } + + private boolean checkFilter(int c) { + return 1 == (1 & (filter >> c)); + } + + @Override + public CharMatcher precomputed() { + return this; + } + + @VisibleForTesting + static char[] buildTable(int modulus, char[] allChars, boolean reprobe) { + char[] table = new char[modulus]; + for (int i = 0; i < allChars.length; i++) { + char c = allChars[i]; + int index = c % modulus; + if (index < 0) { + index += modulus; + } + if ((table[index] != 0) && !reprobe) { + return null; + } else if (reprobe) { + while (table[index] != 0) { + index = (index + 1) % modulus; + } + } + table[index] = c; + } + return table; + } + + static CharMatcher from(char[] chars, String description) { + long filter = 0; + int size = chars.length; + boolean containsZero = false; + boolean reprobe = false; + containsZero = chars[0] == 0; + + // Compute the filter. + for (char c : chars) { + filter |= 1L << c; + } + char[] table = null; + for (int i = size; i < MAX_TABLE_SIZE; i++) { + table = buildTable(i, chars, false); + if (table != null) { + break; + } + } + // Compute the hash table. + if (table == null) { + table = buildTable(MAX_TABLE_SIZE, chars, true); + reprobe = true; + } + return new SmallCharMatcher(table, filter, containsZero, reprobe, description); + } + + @Override + public boolean matches(char c) { + if (c == 0) { + return containsZero; + } + if (!checkFilter(c)) { + return false; + } + int index = c % table.length; + if (index < 0) { + index += table.length; + } + while (true) { + // Check for empty. + if (table[index] == 0) { + return false; + } else if (table[index] == c) { + return true; + } else if (reprobe) { + // Linear probing will terminate eventually. + index = (index + 1) % table.length; + } else { + return false; + } + } + } +} diff --git a/guava/src/com/google/common/base/Splitter.java b/guava/src/com/google/common/base/Splitter.java new file mode 100644 index 0000000..a1c236c --- /dev/null +++ b/guava/src/com/google/common/base/Splitter.java @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.CheckReturnValue; + +/** + * An object that divides strings (or other instances of {@code CharSequence}) + * into substrings, by recognizing a separator (a.k.a. "delimiter") + * which can be expressed as a single character, literal string, regular + * expression, {@code CharMatcher}, or by using a fixed substring length. This + * class provides the complementary functionality to {@link Joiner}. + * + *

Here is the most basic example of {@code Splitter} usage:

   {@code
+ *
+ *   Splitter.on(',').split("foo,bar")}
+ * + * This invocation returns an {@code Iterable} containing {@code "foo"} + * and {@code "bar"}, in that order. + * + *

By default {@code Splitter}'s behavior is very simplistic:

   {@code
+ *
+ *   Splitter.on(',').split("foo,,bar, quux")}
+ * + * This returns an iterable containing {@code ["foo", "", "bar", " quux"]}. + * Notice that the splitter does not assume that you want empty strings removed, + * or that you wish to trim whitespace. If you want features like these, simply + * ask for them:
 {@code
+ *
+ *   private static final Splitter MY_SPLITTER = Splitter.on(',')
+ *       .trimResults()
+ *       .omitEmptyStrings();}
+ * + * Now {@code MY_SPLITTER.split("foo, ,bar, quux,")} returns an iterable + * containing just {@code ["foo", "bar", "quux"]}. Note that the order in which + * the configuration methods are called is never significant; for instance, + * trimming is always applied first before checking for an empty result, + * regardless of the order in which the {@link #trimResults()} and + * {@link #omitEmptyStrings()} methods were invoked. + * + *

Warning: splitter instances are always immutable; a configuration + * method such as {@code omitEmptyStrings} has no effect on the instance it + * is invoked on! You must store and use the new splitter instance returned by + * the method. This makes splitters thread-safe, and safe to store as {@code + * static final} constants (as illustrated above).

   {@code
+ *
+ *   // Bad! Do not do this!
+ *   Splitter splitter = Splitter.on('/');
+ *   splitter.trimResults(); // does nothing!
+ *   return splitter.split("wrong / wrong / wrong");}
+ * + * The separator recognized by the splitter does not have to be a single + * literal character as in the examples above. See the methods {@link + * #on(String)}, {@link #on(Pattern)} and {@link #on(CharMatcher)} for examples + * of other ways to specify separators. + * + *

Note: this class does not mimic any of the quirky behaviors of + * similar JDK methods; for instance, it does not silently discard trailing + * separators, as does {@link String#split(String)}, nor does it have a default + * behavior of using five particular whitespace characters as separators, like + * {@link java.util.StringTokenizer}. + * + *

If either {@code trimResults} option is also specified when creating a + * splitter, that splitter always trims results first before checking for + * emptiness. So, for example, {@code + * Splitter.on(':').omitEmptyStrings().trimResults().split(": : : ")} returns + * an empty iterable. + * + *

Note that it is ordinarily not possible for {@link #split(CharSequence)} + * to return an empty iterable, but when using this option, it can (if the + * input sequence consists of nothing but separators). + * + * @return a splitter with the desired configuration + */ + @CheckReturnValue + public Splitter omitEmptyStrings() { + return new Splitter(strategy, true, trimmer, limit); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter but + * stops splitting after it reaches the limit. + * The limit defines the maximum number of items returned by the iterator. + * + *

For example, + * {@code Splitter.on(',').limit(3).split("a,b,c,d")} returns an iterable + * containing {@code ["a", "b", "c,d"]}. When omitting empty strings, the + * omitted strings do no count. Hence, + * {@code Splitter.on(',').limit(3).omitEmptyStrings().split("a,,,b,,,c,d")} + * returns an iterable containing {@code ["a", "b", "c,d"}. + * When trim is requested, all entries, including the last are trimmed. Hence + * {@code Splitter.on(',').limit(3).trimResults().split(" a , b , c , d ")} + * results in @{code ["a", "b", "c , d"]}. + * + * @param limit the maximum number of items returns + * @return a splitter with the desired configuration + * @since 9.0 + */ + @CheckReturnValue + public Splitter limit(int limit) { + checkArgument(limit > 0, "must be greater than zero: %s", limit); + return new Splitter(strategy, omitEmptyStrings, trimmer, limit); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but + * automatically removes leading and trailing {@linkplain + * CharMatcher#WHITESPACE whitespace} from each returned substring; equivalent + * to {@code trimResults(CharMatcher.WHITESPACE)}. For example, {@code + * Splitter.on(',').trimResults().split(" a, b ,c ")} returns an iterable + * containing {@code ["a", "b", "c"]}. + * + * @return a splitter with the desired configuration + */ + @CheckReturnValue + public Splitter trimResults() { + return trimResults(CharMatcher.WHITESPACE); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but + * removes all leading or trailing characters matching the given {@code + * CharMatcher} from each returned substring. For example, {@code + * Splitter.on(',').trimResults(CharMatcher.is('_')).split("_a ,_b_ ,c__")} + * returns an iterable containing {@code ["a ", "b_ ", "c"]}. + * + * @param trimmer a {@link CharMatcher} that determines whether a character + * should be removed from the beginning/end of a subsequence + * @return a splitter with the desired configuration + */ + // TODO(kevinb): throw if a trimmer was already specified! + @CheckReturnValue + public Splitter trimResults(CharMatcher trimmer) { + checkNotNull(trimmer); + return new Splitter(strategy, omitEmptyStrings, trimmer, limit); + } + + /** + * Splits {@code sequence} into string components and makes them available + * through an {@link Iterator}, which may be lazily evaluated. + * + * @param sequence the sequence of characters to split + * @return an iteration over the segments split from the parameter. + */ + public Iterable split(final CharSequence sequence) { + checkNotNull(sequence); + + return new Iterable() { + @Override public Iterator iterator() { + return spliterator(sequence); + } + @Override public String toString() { + return Joiner.on(", ") + .appendTo(new StringBuilder().append('['), this) + .append(']') + .toString(); + } + }; + } + + private Iterator spliterator(CharSequence sequence) { + return strategy.iterator(this, sequence); + } + + /** + * Returns a {@code MapSplitter} which splits entries based on this splitter, + * and splits entries into keys and values using the specified separator. + * + * @since 10.0 + */ + @CheckReturnValue + @Beta + public MapSplitter withKeyValueSeparator(String separator) { + return withKeyValueSeparator(on(separator)); + } + + /** + * Returns a {@code MapSplitter} which splits entries based on this splitter, + * and splits entries into keys and values using the specified key-value + * splitter. + * + * @since 10.0 + */ + @CheckReturnValue + @Beta + public MapSplitter withKeyValueSeparator(Splitter keyValueSplitter) { + return new MapSplitter(this, keyValueSplitter); + } + + /** + * An object that splits strings into maps as {@code Splitter} splits + * iterables and lists. Like {@code Splitter}, it is thread-safe and + * immutable. + * + * @since 10.0 + */ + @Beta + public static final class MapSplitter { + private static final String INVALID_ENTRY_MESSAGE = + "Chunk [%s] is not a valid entry"; + private final Splitter outerSplitter; + private final Splitter entrySplitter; + + private MapSplitter(Splitter outerSplitter, Splitter entrySplitter) { + this.outerSplitter = outerSplitter; // only "this" is passed + this.entrySplitter = checkNotNull(entrySplitter); + } + + /** + * Splits {@code sequence} into substrings, splits each substring into + * an entry, and returns an unmodifiable map with each of the entries. For + * example, + * Splitter.on(';').trimResults().withKeyValueSeparator("=>") + * .split("a=>b ; c=>b") + * will return a mapping from {@code "a"} to {@code "b"} and + * {@code "c"} to {@code b}. + * + *

The returned map preserves the order of the entries from + * {@code sequence}. + * + * @throws IllegalArgumentException if the specified sequence does not split + * into valid map entries, or if there are duplicate keys + */ + public Map split(CharSequence sequence) { + Map map = new LinkedHashMap(); + for (String entry : outerSplitter.split(sequence)) { + Iterator entryFields = entrySplitter.spliterator(entry); + + checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + String key = entryFields.next(); + checkArgument(!map.containsKey(key), "Duplicate key [%s] found.", key); + + checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + String value = entryFields.next(); + map.put(key, value); + + checkArgument(!entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + } + return Collections.unmodifiableMap(map); + } + } + + private interface Strategy { + Iterator iterator(Splitter splitter, CharSequence toSplit); + } + + private abstract static class SplittingIterator extends AbstractIterator { + final CharSequence toSplit; + final CharMatcher trimmer; + final boolean omitEmptyStrings; + + /** + * Returns the first index in {@code toSplit} at or after {@code start} + * that contains the separator. + */ + abstract int separatorStart(int start); + + /** + * Returns the first index in {@code toSplit} after {@code + * separatorPosition} that does not contain a separator. This method is only + * invoked after a call to {@code separatorStart}. + */ + abstract int separatorEnd(int separatorPosition); + + int offset = 0; + int limit; + + protected SplittingIterator(Splitter splitter, CharSequence toSplit) { + this.trimmer = splitter.trimmer; + this.omitEmptyStrings = splitter.omitEmptyStrings; + this.limit = splitter.limit; + this.toSplit = toSplit; + } + + @Override protected String computeNext() { + /* + * The returned string will be from the end of the last match to the + * beginning of the next one. nextStart is the start position of the + * returned substring, while offset is the place to start looking for a + * separator. + */ + int nextStart = offset; + while (offset != -1) { + int start = nextStart; + int end; + + int separatorPosition = separatorStart(offset); + if (separatorPosition == -1) { + end = toSplit.length(); + offset = -1; + } else { + end = separatorPosition; + offset = separatorEnd(separatorPosition); + } + if (offset == nextStart) { + /* + * This occurs when some pattern has an empty match, even if it + * doesn't match the empty string -- for example, if it requires + * lookahead or the like. The offset must be increased to look for + * separators beyond this point, without changing the start position + * of the next returned substring -- so nextStart stays the same. + */ + offset++; + if (offset >= toSplit.length()) { + offset = -1; + } + continue; + } + + while (start < end && trimmer.matches(toSplit.charAt(start))) { + start++; + } + while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { + end--; + } + + if (omitEmptyStrings && start == end) { + // Don't include the (unused) separator in next split string. + nextStart = offset; + continue; + } + + if (limit == 1) { + // The limit has been reached, return the rest of the string as the + // final item. This is tested after empty string removal so that + // empty strings do not count towards the limit. + end = toSplit.length(); + offset = -1; + // Since we may have changed the end, we need to trim it again. + while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { + end--; + } + } else { + limit--; + } + + return toSplit.subSequence(start, end).toString(); + } + return endOfData(); + } + } +} diff --git a/guava/src/com/google/common/base/Stopwatch.java b/guava/src/com/google/common/base/Stopwatch.java new file mode 100644 index 0000000..9e1b6f6 --- /dev/null +++ b/guava/src/com/google/common/base/Stopwatch.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.TimeUnit; + +/** + * An object that measures elapsed time in nanoseconds. It is useful to measure + * elapsed time using this class instead of direct calls to {@link + * System#nanoTime} for a few reasons: + * + *

    + *
  • An alternate time source can be substituted, for testing or performance + * reasons. + *
  • As documented by {@code nanoTime}, the value returned has no absolute + * meaning, and can only be interpreted as relative to another timestamp + * returned by {@code nanoTime} at a different time. {@code Stopwatch} is a + * more effective abstraction because it exposes only these relative values, + * not the absolute ones. + *
+ * + *

Basic usage: + *

+ *   Stopwatch stopwatch = new Stopwatch().{@link #start start}();
+ *   doSomething();
+ *   stopwatch.{@link #stop stop}(); // optional
+ *
+ *   long millis = stopwatch.{@link #elapsedMillis elapsedMillis}();
+ *
+ *   log.info("that took: " + stopwatch); // formatted string like "12.3 ms"
+ * 
+ * + *

Stopwatch methods are not idempotent; it is an error to start or stop a + * stopwatch that is already in the desired state. + * + *

When testing code that uses this class, use the {@linkplain + * #Stopwatch(Ticker) alternate constructor} to supply a fake or mock ticker. + * This allows you to + * simulate any valid behavior of the stopwatch. + * + *

Note: This class is not thread-safe. + * + * @author Kevin Bourrillion + * @since 10.0 + */ +@Beta +@GwtCompatible(emulated=true) +public final class Stopwatch { + private final Ticker ticker; + private boolean isRunning; + private long elapsedNanos; + private long startTick; + + /** + * Creates (but does not start) a new stopwatch using {@link System#nanoTime} + * as its time source. + */ + public Stopwatch() { + this(Ticker.systemTicker()); + } + + /** + * Creates (but does not start) a new stopwatch, using the specified time + * source. + */ + public Stopwatch(Ticker ticker) { + this.ticker = checkNotNull(ticker); + } + + /** + * Returns {@code true} if {@link #start()} has been called on this stopwatch, + * and {@link #stop()} has not been called since the last call to {@code + * start()}. + */ + public boolean isRunning() { + return isRunning; + } + + /** + * Starts the stopwatch. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already running. + */ + public Stopwatch start() { + checkState(!isRunning); + isRunning = true; + startTick = ticker.read(); + return this; + } + + /** + * Stops the stopwatch. Future reads will return the fixed duration that had + * elapsed up to this point. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already stopped. + */ + public Stopwatch stop() { + long tick = ticker.read(); + checkState(isRunning); + isRunning = false; + elapsedNanos += tick - startTick; + return this; + } + + /** + * Sets the elapsed time for this stopwatch to zero, + * and places it in a stopped state. + * + * @return this {@code Stopwatch} instance + */ + public Stopwatch reset() { + elapsedNanos = 0; + isRunning = false; + return this; + } + + private long elapsedNanos() { + return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos; + } + + /** + * Returns the current elapsed time shown on this stopwatch, expressed + * in the desired time unit, with any fraction rounded down. + * + *

Note that the overhead of measurement can be more than a microsecond, so + * it is generally not useful to specify {@link TimeUnit#NANOSECONDS} + * precision here. + */ + public long elapsedTime(TimeUnit desiredUnit) { + return desiredUnit.convert(elapsedNanos(), NANOSECONDS); + } + + /** + * Returns the current elapsed time shown on this stopwatch, expressed + * in milliseconds, with any fraction rounded down. This is identical to + * {@code elapsedTime(TimeUnit.MILLISECONDS}. + */ + public long elapsedMillis() { + return elapsedTime(MILLISECONDS); + } + + /** + * Returns a string representation of the current elapsed time. + */ + @GwtIncompatible("String.format()") + @Override public String toString() { + return toString(4); + } + + /** + * Returns a string representation of the current elapsed time, choosing an + * appropriate unit and using the specified number of significant figures. + * For example, at the instant when {@code elapsedTime(NANOSECONDS)} would + * return {1234567}, {@code toString(4)} returns {@code "1.235 ms"}. + * + * @deprecated Use {@link #toString()} instead. This method is scheduled + * to be removed in Guava release 15.0. + */ + @Deprecated + @GwtIncompatible("String.format()") + public String toString(int significantDigits) { + long nanos = elapsedNanos(); + + TimeUnit unit = chooseUnit(nanos); + double value = (double) nanos / NANOSECONDS.convert(1, unit); + + // Too bad this functionality is not exposed as a regular method call + return String.format("%." + significantDigits + "g %s", + value, abbreviate(unit)); + } + + private static TimeUnit chooseUnit(long nanos) { + if (SECONDS.convert(nanos, NANOSECONDS) > 0) { + return SECONDS; + } + if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) { + return MILLISECONDS; + } + if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) { + return MICROSECONDS; + } + return NANOSECONDS; + } + + private static String abbreviate(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "\u03bcs"; // μs + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + default: + throw new AssertionError(); + } + } +} diff --git a/guava/src/com/google/common/base/Strings.java b/guava/src/com/google/common/base/Strings.java new file mode 100644 index 0000000..45007fd --- /dev/null +++ b/guava/src/com/google/common/base/Strings.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.util.Formatter; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@code String} or {@code CharSequence} + * instances. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@GwtCompatible +public final class Strings { + private Strings() {} + + /** + * Returns the given string if it is non-null; the empty string otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is non-null; {@code ""} if it is null + */ + public static String nullToEmpty(@Nullable String string) { + return (string == null) ? "" : string; + } + + /** + * Returns the given string if it is nonempty; {@code null} otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is nonempty; {@code null} if it is + * empty or null + */ + public static @Nullable String emptyToNull(@Nullable String string) { + return isNullOrEmpty(string) ? null : string; + } + + /** + * Returns {@code true} if the given string is null or is the empty string. + * + *

Consider normalizing your string references with {@link #nullToEmpty}. + * If you do, you can use {@link String#isEmpty()} instead of this + * method, and you won't need special null-safe forms of methods like {@link + * String#toUpperCase} either. Or, if you'd like to normalize "in the other + * direction," converting empty strings to {@code null}, you can use {@link + * #emptyToNull}. + * + * @param string a string reference to check + * @return {@code true} if the string is null or is the empty string + */ + public static boolean isNullOrEmpty(@Nullable String string) { + return string == null || string.length() == 0; // string.isEmpty() in Java 6 + } + + /** + * Returns a string, of length at least {@code minLength}, consisting of + * {@code string} prepended with as many copies of {@code padChar} as are + * necessary to reach that length. For example, + * + *

    + *
  • {@code padStart("7", 3, '0')} returns {@code "007"} + *
  • {@code padStart("2010", 3, '0')} returns {@code "2010"} + *
+ * + *

See {@link Formatter} for a richer set of formatting capabilities. + * + * @param string the string which should appear at the end of the result + * @param minLength the minimum length the resulting string must have. Can be + * zero or negative, in which case the input string is always returned. + * @param padChar the character to insert at the beginning of the result until + * the minimum length is reached + * @return the padded string + */ + public static String padStart(String string, int minLength, char padChar) { + checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + sb.append(string); + return sb.toString(); + } + + /** + * Returns a string, of length at least {@code minLength}, consisting of + * {@code string} appended with as many copies of {@code padChar} as are + * necessary to reach that length. For example, + * + *

    + *
  • {@code padEnd("4.", 5, '0')} returns {@code "4.000"} + *
  • {@code padEnd("2010", 3, '!')} returns {@code "2010"} + *
+ * + *

See {@link Formatter} for a richer set of formatting capabilities. + * + * @param string the string which should appear at the beginning of the result + * @param minLength the minimum length the resulting string must have. Can be + * zero or negative, in which case the input string is always returned. + * @param padChar the character to append to the end of the result until the + * minimum length is reached + * @return the padded string + */ + public static String padEnd(String string, int minLength, char padChar) { + checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + sb.append(string); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + return sb.toString(); + } + + /** + * Returns a string consisting of a specific number of concatenated copies of + * an input string. For example, {@code repeat("hey", 3)} returns the string + * {@code "heyheyhey"}. + * + * @param string any non-null string + * @param count the number of times to repeat it; a nonnegative integer + * @return a string containing {@code string} repeated {@code count} times + * (the empty string if {@code count} is zero) + * @throws IllegalArgumentException if {@code count} is negative + */ + public static String repeat(String string, int count) { + checkNotNull(string); // eager for GWT. + + if (count <= 1) { + checkArgument(count >= 0, "invalid count: %s", count); + return (count == 0) ? "" : string; + } + + // IF YOU MODIFY THE CODE HERE, you must update StringsRepeatBenchmark + final int len = string.length(); + final long longSize = (long) len * (long) count; + final int size = (int) longSize; + if (size != longSize) { + throw new ArrayIndexOutOfBoundsException("Required array size too large: " + + String.valueOf(longSize)); + } + + final char[] array = new char[size]; + string.getChars(0, len, array, 0); + int n; + for (n = len; n < size - n; n <<= 1) { + System.arraycopy(array, 0, array, n, n); + } + System.arraycopy(array, 0, array, n, size - n); + return new String(array); + } + + /** + * Returns the longest string {@code prefix} such that + * {@code a.toString().startsWith(prefix) && b.toString().startsWith(prefix)}, + * taking care not to split surrogate pairs. If {@code a} and {@code b} have + * no common prefix, returns the empty string. + * + * @since 11.0 + */ + public static String commonPrefix(CharSequence a, CharSequence b) { + checkNotNull(a); + checkNotNull(b); + + int maxPrefixLength = Math.min(a.length(), b.length()); + int p = 0; + while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) { + p++; + } + if (validSurrogatePairAt(a, p - 1) || validSurrogatePairAt(b, p - 1)) { + p--; + } + return a.subSequence(0, p).toString(); + } + + /** + * Returns the longest string {@code suffix} such that + * {@code a.toString().endsWith(suffix) && b.toString().endsWith(suffix)}, + * taking care not to split surrogate pairs. If {@code a} and {@code b} have + * no common suffix, returns the empty string. + * + * @since 11.0 + */ + public static String commonSuffix(CharSequence a, CharSequence b) { + checkNotNull(a); + checkNotNull(b); + + int maxSuffixLength = Math.min(a.length(), b.length()); + int s = 0; + while (s < maxSuffixLength + && a.charAt(a.length() - s - 1) == b.charAt(b.length() - s - 1)) { + s++; + } + if (validSurrogatePairAt(a, a.length() - s - 1) + || validSurrogatePairAt(b, b.length() - s - 1)) { + s--; + } + return a.subSequence(a.length() - s, a.length()).toString(); + } + + /** + * True when a valid surrogate pair starts at the given {@code index} in the + * given {@code string}. Out-of-range indexes return false. + */ + @VisibleForTesting + static boolean validSurrogatePairAt(CharSequence string, int index) { + return index >= 0 && index <= (string.length() - 2) + && Character.isHighSurrogate(string.charAt(index)) + && Character.isLowSurrogate(string.charAt(index + 1)); + } +} diff --git a/guava/src/com/google/common/base/Supplier.java b/guava/src/com/google/common/base/Supplier.java new file mode 100644 index 0000000..ab8b908 --- /dev/null +++ b/guava/src/com/google/common/base/Supplier.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * A class that can supply objects of a single type. Semantically, this could + * be a factory, generator, builder, closure, or something else entirely. No + * guarantees are implied by this interface. + * + * @author Harry Heymann + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface Supplier { + /** + * Retrieves an instance of the appropriate type. The returned object may or + * may not be a new instance, depending on the implementation. + * + * @return an instance of the appropriate type + */ + T get(); +} diff --git a/guava/src/com/google/common/base/Suppliers.java b/guava/src/com/google/common/base/Suppliers.java new file mode 100644 index 0000000..add5117 --- /dev/null +++ b/guava/src/com/google/common/base/Suppliers.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +/** + * Useful suppliers. + * + *

All methods return serializable suppliers as long as they're given + * serializable parameters. + * + * @author Laurence Gonsalves + * @author Harry Heymann + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Suppliers { + private Suppliers() {} + + /** + * Returns a new supplier which is the composition of the provided function + * and supplier. In other words, the new supplier's value will be computed by + * retrieving the value from {@code supplier}, and then applying + * {@code function} to that value. Note that the resulting supplier will not + * call {@code supplier} or invoke {@code function} until it is called. + */ + public static Supplier compose( + Function function, Supplier supplier) { + Preconditions.checkNotNull(function); + Preconditions.checkNotNull(supplier); + return new SupplierComposition(function, supplier); + } + + private static class SupplierComposition + implements Supplier, Serializable { + final Function function; + final Supplier supplier; + + SupplierComposition(Function function, Supplier supplier) { + this.function = function; + this.supplier = supplier; + } + + @Override + public T get() { + return function.apply(supplier.get()); + } + + @Override + public String toString() { + return "Suppliers.compose(" + function + ", " + supplier + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier which caches the instance retrieved during the first + * call to {@code get()} and returns that value on subsequent calls to + * {@code get()}. See: + * memoization + * + *

The returned supplier is thread-safe. The supplier's serialized form + * does not contain the cached value, which will be recalculated when {@code + * get()} is called on the reserialized instance. + * + *

If {@code delegate} is an instance created by an earlier call to {@code + * memoize}, it is returned directly. + */ + public static Supplier memoize(Supplier delegate) { + return (delegate instanceof MemoizingSupplier) + ? delegate + : new MemoizingSupplier(Preconditions.checkNotNull(delegate)); + } + + @VisibleForTesting + static class MemoizingSupplier implements Supplier, Serializable { + final Supplier delegate; + transient volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + transient T value; + + MemoizingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + return "Suppliers.memoize(" + delegate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier that caches the instance supplied by the delegate and + * removes the cached value after the specified time has passed. Subsequent + * calls to {@code get()} return the cached value if the expiration time has + * not passed. After the expiration time, a new value is retrieved, cached, + * and returned. See: + * memoization + * + *

The returned supplier is thread-safe. The supplier's serialized form + * does not contain the cached value, which will be recalculated when {@code + * get()} is called on the reserialized instance. + * + * @param duration the length of time after a value is created that it + * should stop being returned by subsequent {@code get()} calls + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is not positive + * @since 2.0 + */ + public static Supplier memoizeWithExpiration( + Supplier delegate, long duration, TimeUnit unit) { + return new ExpiringMemoizingSupplier(delegate, duration, unit); + } + + @VisibleForTesting static class ExpiringMemoizingSupplier + implements Supplier, Serializable { + final Supplier delegate; + final long durationNanos; + transient volatile T value; + // The special value 0 means "not yet initialized". + transient volatile long expirationNanos; + + ExpiringMemoizingSupplier( + Supplier delegate, long duration, TimeUnit unit) { + this.delegate = Preconditions.checkNotNull(delegate); + this.durationNanos = unit.toNanos(duration); + Preconditions.checkArgument(duration > 0); + } + + @Override + public T get() { + // Another variant of Double Checked Locking. + // + // We use two volatile reads. We could reduce this to one by + // putting our fields into a holder class, but (at least on x86) + // the extra memory consumption and indirection are more + // expensive than the extra volatile reads. + long nanos = expirationNanos; + long now = Platform.systemNanoTime(); + if (nanos == 0 || now - nanos >= 0) { + synchronized (this) { + if (nanos == expirationNanos) { // recheck for lost race + T t = delegate.get(); + value = t; + nanos = now + durationNanos; + // In the very unlikely event that nanos is 0, set it to 1; + // no one will notice 1 ns of tardiness. + expirationNanos = (nanos == 0) ? 1 : nanos; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + // This is a little strange if the unit the user provided was not NANOS, + // but we don't want to store the unit just for toString + return "Suppliers.memoizeWithExpiration(" + delegate + ", " + + durationNanos + ", NANOS)"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier that always supplies {@code instance}. + */ + public static Supplier ofInstance(@Nullable T instance) { + return new SupplierOfInstance(instance); + } + + private static class SupplierOfInstance + implements Supplier, Serializable { + final T instance; + + SupplierOfInstance(@Nullable T instance) { + this.instance = instance; + } + + @Override + public T get() { + return instance; + } + + @Override + public String toString() { + return "Suppliers.ofInstance(" + instance + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier whose {@code get()} method synchronizes on + * {@code delegate} before calling it, making it thread-safe. + */ + public static Supplier synchronizedSupplier(Supplier delegate) { + return new ThreadSafeSupplier(Preconditions.checkNotNull(delegate)); + } + + private static class ThreadSafeSupplier + implements Supplier, Serializable { + final Supplier delegate; + + ThreadSafeSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Override + public T get() { + synchronized (delegate) { + return delegate.get(); + } + } + + @Override + public String toString() { + return "Suppliers.synchronizedSupplier(" + delegate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function that accepts a supplier and returns the result of + * invoking {@link Supplier#get} on that supplier. + * + * @since 8.0 + */ + @Beta + @SuppressWarnings("unchecked") // SupplierFunction works for any T. + public static Function, T> supplierFunction() { + return (Function) SupplierFunction.INSTANCE; + } + + private enum SupplierFunction implements Function, Object> { + INSTANCE; + + @Override + public Object apply(Supplier input) { + return input.get(); + } + + @Override + public String toString() { + return "Suppliers.supplierFunction()"; + } + } +} diff --git a/guava/src/com/google/common/base/Throwables.java b/guava/src/com/google/common/base/Throwables.java new file mode 100644 index 0000000..5e4d6ec --- /dev/null +++ b/guava/src/com/google/common/base/Throwables.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to instances of {@link Throwable}. + * + *

See the Guava User Guide entry on + * Throwables. + * + * @author Kevin Bourrillion + * @author Ben Yu + * @since 1.0 + */ +public final class Throwables { + private Throwables() {} + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@code declaredType}. Example usage: + *

+   *   try {
+   *     someMethodThatCouldThrowAnything();
+   *   } catch (IKnowWhatToDoWithThisException e) {
+   *     handle(e);
+   *   } catch (Throwable t) {
+   *     Throwables.propagateIfInstanceOf(t, IOException.class);
+   *     Throwables.propagateIfInstanceOf(t, SQLException.class);
+   *     throw Throwables.propagate(t);
+   *   }
+   * 
+ */ + public static void propagateIfInstanceOf( + @Nullable Throwable throwable, Class declaredType) throws X { + // Check for null is needed to avoid frequent JNI calls to isInstance(). + if (throwable != null && declaredType.isInstance(throwable)) { + throw declaredType.cast(throwable); + } + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException} or {@link Error}. Example usage: + *
+   *   try {
+   *     someMethodThatCouldThrowAnything();
+   *   } catch (IKnowWhatToDoWithThisException e) {
+   *     handle(e);
+   *   } catch (Throwable t) {
+   *     Throwables.propagateIfPossible(t);
+   *     throw new RuntimeException("unexpected", t);
+   *   }
+   * 
+ */ + public static void propagateIfPossible(@Nullable Throwable throwable) { + propagateIfInstanceOf(throwable, Error.class); + propagateIfInstanceOf(throwable, RuntimeException.class); + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException}, {@link Error}, or + * {@code declaredType}. Example usage: + *
+   *   try {
+   *     someMethodThatCouldThrowAnything();
+   *   } catch (IKnowWhatToDoWithThisException e) {
+   *     handle(e);
+   *   } catch (Throwable t) {
+   *     Throwables.propagateIfPossible(t, OtherException.class);
+   *     throw new RuntimeException("unexpected", t);
+   *   }
+   * 
+ * + * @param throwable the Throwable to possibly propagate + * @param declaredType the single checked exception type declared by the + * calling method + */ + public static void propagateIfPossible( + @Nullable Throwable throwable, Class declaredType) throws X { + propagateIfInstanceOf(throwable, declaredType); + propagateIfPossible(throwable); + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException}, {@link Error}, {@code declaredType1}, + * or {@code declaredType2}. In the unlikely case that you have three or more + * declared checked exception types, you can handle them all by invoking these + * methods repeatedly. See usage example in {@link + * #propagateIfPossible(Throwable, Class)}. + * + * @param throwable the Throwable to possibly propagate + * @param declaredType1 any checked exception type declared by the calling + * method + * @param declaredType2 any other checked exception type declared by the + * calling method + */ + public static + void propagateIfPossible(@Nullable Throwable throwable, + Class declaredType1, Class declaredType2) throws X1, X2 { + checkNotNull(declaredType2); + propagateIfInstanceOf(throwable, declaredType1); + propagateIfPossible(throwable, declaredType2); + } + + /** + * Propagates {@code throwable} as-is if it is an instance of + * {@link RuntimeException} or {@link Error}, or else as a last resort, wraps + * it in a {@code RuntimeException} then propagates. + *

+ * This method always throws an exception. The {@code RuntimeException} return + * type is only for client code to make Java type system happy in case a + * return value is required by the enclosing method. Example usage: + *

+   *   T doSomething() {
+   *     try {
+   *       return someMethodThatCouldThrowAnything();
+   *     } catch (IKnowWhatToDoWithThisException e) {
+   *       return handle(e);
+   *     } catch (Throwable t) {
+   *       throw Throwables.propagate(t);
+   *     }
+   *   }
+   * 
+ * + * @param throwable the Throwable to propagate + * @return nothing will ever be returned; this return type is only for your + * convenience, as illustrated in the example above + */ + public static RuntimeException propagate(Throwable throwable) { + propagateIfPossible(checkNotNull(throwable)); + throw new RuntimeException(throwable); + } + + /** + * Returns the innermost cause of {@code throwable}. The first throwable in a + * chain provides context from when the error or exception was initially + * detected. Example usage: + *
+   *   assertEquals("Unable to assign a customer id",
+   *       Throwables.getRootCause(e).getMessage());
+   * 
+ */ + public static Throwable getRootCause(Throwable throwable) { + Throwable cause; + while ((cause = throwable.getCause()) != null) { + throwable = cause; + } + return throwable; + } + + /** + * Gets a {@code Throwable} cause chain as a list. The first entry in the + * list will be {@code throwable} followed by its cause hierarchy. Note + * that this is a snapshot of the cause chain and will not reflect + * any subsequent changes to the cause chain. + * + *

Here's an example of how it can be used to find specific types + * of exceptions in the cause chain: + * + *

+   * Iterables.filter(Throwables.getCausalChain(e), IOException.class));
+   * 
+ * + * @param throwable the non-null {@code Throwable} to extract causes from + * @return an unmodifiable list containing the cause chain starting with + * {@code throwable} + */ + @Beta // TODO(kevinb): decide best return type + public static List getCausalChain(Throwable throwable) { + checkNotNull(throwable); + List causes = new ArrayList(4); + while (throwable != null) { + causes.add(throwable); + throwable = throwable.getCause(); + } + return Collections.unmodifiableList(causes); + } + + /** + * Returns a string containing the result of + * {@link Throwable#toString() toString()}, followed by the full, recursive + * stack trace of {@code throwable}. Note that you probably should not be + * parsing the resulting string; if you need programmatic access to the stack + * frames, you can call {@link Throwable#getStackTrace()}. + */ + public static String getStackTraceAsString(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } +} diff --git a/guava/src/com/google/common/base/Ticker.java b/guava/src/com/google/common/base/Ticker.java new file mode 100644 index 0000000..6c34aef --- /dev/null +++ b/guava/src/com/google/common/base/Ticker.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * A time source; returns a time value representing the number of nanoseconds elapsed since some + * fixed but arbitrary point in time. Note that most users should use {@link Stopwatch} instead of + * interacting with this class directly. + * + *

Warning: this interface can only be used to measure elapsed time, not wall time. + * + * @author Kevin Bourrillion + * @since 10.0 + * (mostly source-compatible since 9.0) + */ +@Beta +@GwtCompatible +public abstract class Ticker { + /** + * Constructor for use by subclasses. + */ + protected Ticker() {} + + /** + * Returns the number of nanoseconds elapsed since this ticker's fixed + * point of reference. + */ + public abstract long read(); + + /** + * A ticker that reads the current time using {@link System#nanoTime}. + * + * @since 10.0 + */ + public static Ticker systemTicker() { + return SYSTEM_TICKER; + } + + private static final Ticker SYSTEM_TICKER = new Ticker() { + @Override + public long read() { + return Platform.systemNanoTime(); + } + }; +} diff --git a/guava/src/com/google/common/base/internal/Finalizer.java b/guava/src/com/google/common/base/internal/Finalizer.java new file mode 100644 index 0000000..0934aa6 --- /dev/null +++ b/guava/src/com/google/common/base/internal/Finalizer.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base.internal; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Thread that finalizes referents. All references should implement + * {@code com.google.common.base.FinalizableReference}. + * + *

While this class is public, we consider it to be *internal* and not part + * of our published API. It is public so we can access it reflectively across + * class loaders in secure environments. + * + *

This class can't depend on other Google Collections code. If we were + * to load this class in the same class loader as the rest of + * Google Collections, this thread would keep an indirect strong reference + * to the class loader and prevent it from being garbage collected. This + * poses a problem for environments where you want to throw away the class + * loader. For example, dynamically reloading a web application or unloading + * an OSGi bundle. + * + *

{@code com.google.common.base.FinalizableReferenceQueue} loads this class + * in its own class loader. That way, this class doesn't prevent the main + * class loader from getting garbage collected, and this class can detect when + * the main class loader has been garbage collected and stop itself. + */ +public class Finalizer implements Runnable { + + private static final Logger logger + = Logger.getLogger(Finalizer.class.getName()); + + /** Name of FinalizableReference.class. */ + private static final String FINALIZABLE_REFERENCE + = "com.google.common.base.FinalizableReference"; + + /** + * Starts the Finalizer thread. FinalizableReferenceQueue calls this method + * reflectively. + * + * @param finalizableReferenceClass FinalizableReference.class + * @param frq reference to instance of FinalizableReferenceQueue that started + * this thread + * @return ReferenceQueue which Finalizer will poll + */ + public static ReferenceQueue startFinalizer( + Class finalizableReferenceClass, Object frq) { + /* + * We use FinalizableReference.class for two things: + * + * 1) To invoke FinalizableReference.finalizeReferent() + * + * 2) To detect when FinalizableReference's class loader has to be garbage + * collected, at which point, Finalizer can stop running + */ + if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) { + throw new IllegalArgumentException( + "Expected " + FINALIZABLE_REFERENCE + "."); + } + + Finalizer finalizer = new Finalizer(finalizableReferenceClass, frq); + Thread thread = new Thread(finalizer); + thread.setName(Finalizer.class.getName()); + thread.setDaemon(true); + + try { + if (inheritableThreadLocals != null) { + inheritableThreadLocals.set(thread, null); + } + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to clear thread local values inherited" + + " by reference finalizer thread.", t); + } + + thread.start(); + return finalizer.queue; + } + + private final WeakReference> finalizableReferenceClassReference; + private final PhantomReference frqReference; + private final ReferenceQueue queue = new ReferenceQueue(); + + private static final Field inheritableThreadLocals + = getInheritableThreadLocalsField(); + + /** Constructs a new finalizer thread. */ + private Finalizer(Class finalizableReferenceClass, Object frq) { + this.finalizableReferenceClassReference + = new WeakReference>(finalizableReferenceClass); + + // Keep track of the FRQ that started us so we know when to stop. + this.frqReference = new PhantomReference(frq, queue); + } + + /** + * Loops continuously, pulling references off the queue and cleaning them up. + */ + @SuppressWarnings("InfiniteLoopStatement") + @Override + public void run() { + try { + while (true) { + try { + cleanUp(queue.remove()); + } catch (InterruptedException e) { /* ignore */ } + } + } catch (ShutDown shutDown) { /* ignore */ } + } + + /** + * Cleans up a single reference. Catches and logs all throwables. + */ + private void cleanUp(Reference reference) throws ShutDown { + Method finalizeReferentMethod = getFinalizeReferentMethod(); + do { + /* + * This is for the benefit of phantom references. Weak and soft + * references will have already been cleared by this point. + */ + reference.clear(); + + if (reference == frqReference) { + /* + * The client no longer has a reference to the + * FinalizableReferenceQueue. We can stop. + */ + throw new ShutDown(); + } + + try { + finalizeReferentMethod.invoke(reference); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + + /* + * Loop as long as we have references available so as not to waste + * CPU looking up the Method over and over again. + */ + } while ((reference = queue.poll()) != null); + } + + /** + * Looks up FinalizableReference.finalizeReferent() method. + */ + private Method getFinalizeReferentMethod() throws ShutDown { + Class finalizableReferenceClass + = finalizableReferenceClassReference.get(); + if (finalizableReferenceClass == null) { + /* + * FinalizableReference's class loader was reclaimed. While there's a + * chance that other finalizable references could be enqueued + * subsequently (at which point the class loader would be resurrected + * by virtue of us having a strong reference to it), we should pretty + * much just shut down and make sure we don't keep it alive any longer + * than necessary. + */ + throw new ShutDown(); + } + try { + return finalizableReferenceClass.getMethod("finalizeReferent"); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + public static Field getInheritableThreadLocalsField() { + try { + Field inheritableThreadLocals + = Thread.class.getDeclaredField("inheritableThreadLocals"); + inheritableThreadLocals.setAccessible(true); + return inheritableThreadLocals; + } catch (Throwable t) { + logger.log(Level.INFO, "Couldn't access Thread.inheritableThreadLocals." + + " Reference finalizer threads will inherit thread local" + + " values."); + return null; + } + } + + /** Indicates that it's time to shut down the Finalizer. */ + @SuppressWarnings("serial") // Never serialized or thrown out of this class. + private static class ShutDown extends Exception {} +} diff --git a/guava/src/com/google/common/base/package-info.java b/guava/src/com/google/common/base/package-info.java new file mode 100644 index 0000000..66e7177 --- /dev/null +++ b/guava/src/com/google/common/base/package-info.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Basic utility libraries and interfaces. + * + *

This package is a part of the open-source + * Guava libraries. + * + *

Contents

+ * + *

String-related utilities

+ * + *
    + *
  • {@link com.google.common.base.Ascii} + *
  • {@link com.google.common.base.CaseFormat} + *
  • {@link com.google.common.base.CharMatcher} + *
  • {@link com.google.common.base.Charsets} + *
  • {@link com.google.common.base.Joiner} + *
  • {@link com.google.common.base.Splitter} + *
  • {@link com.google.common.base.Strings} + *
+ * + *

Function types

+ * + *
    + *
  • {@link com.google.common.base.Function}, + * {@link com.google.common.base.Functions} + *
  • {@link com.google.common.base.Predicate}, + * {@link com.google.common.base.Predicates} + *
  • {@link com.google.common.base.Equivalence}, + * {@link com.google.common.base.Equivalences} + *
  • {@link com.google.common.base.Supplier}, + * {@link com.google.common.base.Suppliers} + *
+ * + *

Other

+ * + *
    + *
  • {@link com.google.common.base.Defaults} + *
  • {@link com.google.common.base.Enums} + *
  • {@link com.google.common.base.Objects} + *
  • {@link com.google.common.base.Optional} + *
  • {@link com.google.common.base.Preconditions} + *
  • {@link com.google.common.base.Stopwatch} + *
  • {@link com.google.common.base.Throwables} + *
+ * + */ +@ParametersAreNonnullByDefault +package com.google.common.base; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/guava/src/com/google/common/cache/AbstractCache.java b/guava/src/com/google/common/cache/AbstractCache.java new file mode 100644 index 0000000..0d98a2d --- /dev/null +++ b/guava/src/com/google/common/cache/AbstractCache.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +/** + * This class provides a skeletal implementation of the {@code Cache} interface to minimize the + * effort required to implement this interface. + * + *

To implement a cache, the programmer needs only to extend this class and provide an + * implementation for the {@link #put} and {@link #getIfPresent} methods. {@link #getAllPresent} is + * implemented in terms of {@link #getIfPresent}; {@link #putAll} is implemented in terms of + * {@link #put}, {@link #invalidateAll(Iterable)} is implemented in terms of {@link #invalidate}. + * The method {@link #cleanUp} is a no-op. All other methods throw an + * {@link UnsupportedOperationException}. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +@GwtCompatible +public abstract class AbstractCache implements Cache { + + /** Constructor for use by subclasses. */ + protected AbstractCache() {} + + /** + * @since 11.0 + */ + @Override + public V get(K key, Callable valueLoader) throws ExecutionException { + throw new UnsupportedOperationException(); + } + + /** + * This implementation of {@code getAllPresent} lacks any insight into the internal cache data + * structure, and is thus forced to return the query keys instead of the cached keys. This is only + * possible with an unsafe cast which requires {@code keys} to actually be of type {@code K}. + * + * {@inheritDoc} + * + * @since 11.0 + */ + @Override + public ImmutableMap getAllPresent(Iterable keys) { + Map result = Maps.newLinkedHashMap(); + for (Object key : keys) { + if (!result.containsKey(key)) { + @SuppressWarnings("unchecked") + K castKey = (K) key; + result.put(castKey, getIfPresent(key)); + } + } + return ImmutableMap.copyOf(result); + } + + /** + * @since 11.0 + */ + @Override + public void put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * @since 12.0 + */ + @Override + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void cleanUp() {} + + @Override + public long size() { + throw new UnsupportedOperationException(); + } + + @Override + public void invalidate(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * @since 11.0 + */ + @Override + public void invalidateAll(Iterable keys) { + for (Object key : keys) { + invalidate(key); + } + } + + @Override + public void invalidateAll() { + throw new UnsupportedOperationException(); + } + + @Override + public CacheStats stats() { + throw new UnsupportedOperationException(); + } + + @Override + public ConcurrentMap asMap() { + throw new UnsupportedOperationException(); + } + + /** + * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link + * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors. + * + * @since 10.0 + */ + @Beta + public interface StatsCounter { + /** + * Records cache hits. This should be called when a cache request returns a cached value. + * + * @param count the number of hits to record + * @since 11.0 + */ + public void recordHits(int count); + + /** + * Records cache misses. This should be called when a cache request returns a value that was + * not found in the cache. This method should be called by the loading thread, as well as by + * threads blocking on the load. Multiple concurrent calls to {@link Cache} lookup methods with + * the same key on an absent value should result in a single call to either + * {@code recordLoadSuccess} or {@code recordLoadException} and multiple calls to this method, + * despite all being served by the results of a single load operation. + * + * @param count the number of misses to record + * @since 11.0 + */ + public void recordMisses(int count); + + /** + * Records the successful load of a new entry. This should be called when a cache request + * causes an entry to be loaded, and the loading completes successfully. In contrast to + * {@link #recordMisses}, this method should only be called by the loading thread. + * + * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new + * value + */ + public void recordLoadSuccess(long loadTime); + + /** + * Records the failed load of a new entry. This should be called when a cache request causes + * an entry to be loaded, but an exception is thrown while loading the entry. In contrast to + * {@link #recordMisses}, this method should only be called by the loading thread. + * + * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new + * value prior to an exception being thrown + */ + public void recordLoadException(long loadTime); + + /** + * Records the eviction of an entry from the cache. This should only been called when an entry + * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain + * Cache#invalidate invalidations}. + */ + public void recordEviction(); + + /** + * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as + * it may be interleaved with update operations. + */ + public CacheStats snapshot(); + } + + /** + * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors. + * + * @since 10.0 + */ + @Beta + public static final class SimpleStatsCounter implements StatsCounter { + private final LongAdder hitCount = new LongAdder(); + private final LongAdder missCount = new LongAdder(); + private final LongAdder loadSuccessCount = new LongAdder(); + private final LongAdder loadExceptionCount = new LongAdder(); + private final LongAdder totalLoadTime = new LongAdder(); + private final LongAdder evictionCount = new LongAdder(); + + /** + * Constructs an instance with all counts initialized to zero. + */ + public SimpleStatsCounter() {} + + /** + * @since 11.0 + */ + @Override + public void recordHits(int count) { + hitCount.add(count); + } + + /** + * @since 11.0 + */ + @Override + public void recordMisses(int count) { + missCount.add(count); + } + + @Override + public void recordLoadSuccess(long loadTime) { + loadSuccessCount.increment(); + totalLoadTime.add(loadTime); + } + + @Override + public void recordLoadException(long loadTime) { + loadExceptionCount.increment(); + totalLoadTime.add(loadTime); + } + + @Override + public void recordEviction() { + evictionCount.increment(); + } + + @Override + public CacheStats snapshot() { + return new CacheStats( + hitCount.sum(), + missCount.sum(), + loadSuccessCount.sum(), + loadExceptionCount.sum(), + totalLoadTime.sum(), + evictionCount.sum()); + } + + /** + * Increments all counters by the values in {@code other}. + */ + public void incrementBy(StatsCounter other) { + CacheStats otherStats = other.snapshot(); + hitCount.add(otherStats.hitCount()); + missCount.add(otherStats.missCount()); + loadSuccessCount.add(otherStats.loadSuccessCount()); + loadExceptionCount.add(otherStats.loadExceptionCount()); + totalLoadTime.add(otherStats.totalLoadTime()); + evictionCount.add(otherStats.evictionCount()); + } + } +} diff --git a/guava/src/com/google/common/cache/AbstractLoadingCache.java b/guava/src/com/google/common/cache/AbstractLoadingCache.java new file mode 100644 index 0000000..6a12c40 --- /dev/null +++ b/guava/src/com/google/common/cache/AbstractLoadingCache.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +/** + * This class provides a skeletal implementation of the {@code Cache} interface to minimize the + * effort required to implement this interface. + * + *

To implement a cache, the programmer needs only to extend this class and provide an + * implementation for the {@link #get(Object)} and {@link #getIfPresent} methods. + * {@link #getUnchecked}, {@link #get(Object, Callable)}, and {@link #getAll} are implemented in + * terms of {@code get}; {@link #getAllPresent} is implemented in terms of {@code getIfPresent}; + * {@link #putAll} is implemented in terms of {@link #put}, {@link #invalidateAll(Iterable)} is + * implemented in terms of {@link #invalidate}. The method {@link #cleanUp} is a no-op. All other + * methods throw an {@link UnsupportedOperationException}. + * + * @author Charles Fry + * @since 11.0 + */ +@Beta +public abstract class AbstractLoadingCache + extends AbstractCache implements LoadingCache { + + /** Constructor for use by subclasses. */ + protected AbstractLoadingCache() {} + + @Override + public V getUnchecked(K key) { + try { + return get(key); + } catch (ExecutionException e) { + throw new UncheckedExecutionException(e.getCause()); + } + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + Map result = Maps.newLinkedHashMap(); + for (K key : keys) { + if (!result.containsKey(key)) { + result.put(key, get(key)); + } + } + return ImmutableMap.copyOf(result); + } + + @Override + public final V apply(K key) { + return getUnchecked(key); + } + + @Override + public void refresh(K key) { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/cache/Cache.java b/guava/src/com/google/common/cache/Cache.java new file mode 100644 index 0000000..cfe5764 --- /dev/null +++ b/guava/src/com/google/common/cache/Cache.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +import javax.annotation.Nullable; + +/** + * A semi-persistent mapping from keys to values. Cache entries are manually added using + * {@link #get(Object, Callable)} or {@link #put(Object, Object)}, and are stored in the cache until + * either evicted or manually invalidated. + * + *

Implementations of this interface are expected to be thread-safe, and can be safely accessed + * by multiple concurrent threads. + * + *

Note that while this class is still annotated as {@link Beta}, the API is frozen from a + * consumer's standpoint. In other words existing methods are all considered {@code non-Beta} and + * won't be changed without going through an 18 month deprecation cycle; however new methods may be + * added at any time. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +@GwtCompatible +public interface Cache { + + /** + * Returns the value associated with {@code key} in this cache, or {@code null} if there is no + * cached value for {@code key}. + * + * @since 11.0 + */ + @Nullable + V getIfPresent(Object key); + + /** + * Returns the value associated with {@code key} in this cache, obtaining that value from + * {@code valueLoader} if necessary. No observable state associated with this cache is modified + * until loading completes. This method provides a simple substitute for the conventional + * "if cached, return; otherwise create, cache and return" pattern. + * + *

Warning: as with {@link CacheLoader#load}, {@code valueLoader} must not return + * {@code null}; it may either return a non-null value or throw an exception. + * + * @throws ExecutionException if a checked exception was thrown while loading the value + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * value + * @throws ExecutionError if an error was thrown while loading the value + * + * @since 11.0 + */ + V get(K key, Callable valueLoader) throws ExecutionException; + + /** + * Returns a map of the values associated with {@code keys} in this cache. The returned map will + * only contain entries which are already present in the cache. + * + * @since 11.0 + */ + ImmutableMap getAllPresent(Iterable keys); + + /** + * Associates {@code value} with {@code key} in this cache. If the cache previously contained a + * value associated with {@code key}, the old value is replaced by {@code value}. + * + *

Prefer {@link #get(Object, Callable)} when using the conventional "if cached, return; + * otherwise create, cache and return" pattern. + * + * @since 11.0 + */ + void put(K key, V value); + + /** + * Copies all of the mappings from the specified map to the cache. The effect of this call is + * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key + * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined + * if the specified map is modified while the operation is in progress. + * + * @since 12.0 + */ + void putAll(Map m); + + /** + * Discards any cached value for key {@code key}. + */ + void invalidate(Object key); + + /** + * Discards any cached values for keys {@code keys}. + * + * @since 11.0 + */ + void invalidateAll(Iterable keys); + + /** + * Discards all entries in the cache. + */ + void invalidateAll(); + + /** + * Returns the approximate number of entries in this cache. + */ + long size(); + + /** + * Returns a current snapshot of this cache's cumulative statistics. All stats are initialized + * to zero, and are monotonically increasing over the lifetime of the cache. + */ + CacheStats stats(); + + /** + * Returns a view of the entries stored in this cache as a thread-safe map. Modifications made to + * the map directly affect the cache. + */ + ConcurrentMap asMap(); + + /** + * Performs any pending maintenance operations needed by the cache. Exactly which activities are + * performed -- if any -- is implementation-dependent. + */ + void cleanUp(); +} diff --git a/guava/src/com/google/common/cache/CacheBuilder.java b/guava/src/com/google/common/cache/CacheBuilder.java new file mode 100644 index 0000000..28a056f --- /dev/null +++ b/guava/src/com/google/common/cache/CacheBuilder.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Objects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Equivalence; +import com.google.common.base.Objects; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.base.Ticker; +import com.google.common.cache.AbstractCache.SimpleStatsCounter; +import com.google.common.cache.AbstractCache.StatsCounter; +import com.google.common.cache.LocalCache.Strength; + +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.ConcurrentModificationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.CheckReturnValue; + +/** + *

A builder of {@link LoadingCache} and {@link Cache} instances having any combination of the + * following features: + * + *

    + *
  • automatic loading of entries into the cache + *
  • least-recently-used eviction when a maximum size is exceeded + *
  • time-based expiration of entries, measured since last access or last write + *
  • keys automatically wrapped in {@linkplain WeakReference weak} references + *
  • values automatically wrapped in {@linkplain WeakReference weak} or + * {@linkplain SoftReference soft} references + *
  • notification of evicted (or otherwise removed) entries + *
  • accumulation of cache access statistics + *
+ * + * These features are all optional; caches can be created using all or none of them. By default + * cache instances created by {@code CacheBuilder} will not perform any type of eviction. + * + *

Usage example:

   {@code
+ *
+ *   LoadingCache graphs = CacheBuilder.newBuilder()
+ *       .maximumSize(10000)
+ *       .expireAfterWrite(10, TimeUnit.MINUTES)
+ *       .removalListener(MY_LISTENER)
+ *       .build(
+ *           new CacheLoader() {
+ *             public Graph load(Key key) throws AnyException {
+ *               return createExpensiveGraph(key);
+ *             }
+ *           });}
+ * + * Or equivalently,
   {@code
+ *
+ *   // In real life this would come from a command-line flag or config file
+ *   String spec = "maximumSize=10000,expireAfterWrite=10m";
+ *
+ *   LoadingCache graphs = CacheBuilder.from(spec)
+ *       .removalListener(MY_LISTENER)
+ *       .build(
+ *           new CacheLoader() {
+ *             public Graph load(Key key) throws AnyException {
+ *               return createExpensiveGraph(key);
+ *             }
+ *           });}
+ * + *

The returned cache is implemented as a hash table with similar performance characteristics to + * {@link ConcurrentHashMap}. It implements all optional operations of the {@link LoadingCache} and + * {@link Cache} interfaces. The {@code asMap} view (and its collection views) have weakly + * consistent iterators. This means that they are safe for concurrent use, but if other threads + * modify the cache after the iterator is created, it is undefined which of these changes, if any, + * are reflected in that iterator. These iterators never throw {@link + * ConcurrentModificationException}. + * + *

Note: by default, the returned cache uses equality comparisons (the + * {@link Object#equals equals} method) to determine equality for keys or values. However, if + * {@link #weakKeys} was specified, the cache uses identity ({@code ==}) + * comparisons instead for keys. Likewise, if {@link #weakValues} or {@link #softValues} was + * specified, the cache uses identity comparisons for values. + * + *

Entries are automatically evicted from the cache when any of + * {@linkplain #maximumSize(long) maximumSize}, {@linkplain #maximumWeight(long) maximumWeight}, + * {@linkplain #expireAfterWrite expireAfterWrite}, + * {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys weakKeys}, + * {@linkplain #weakValues weakValues}, or {@linkplain #softValues softValues} are requested. + * + *

If {@linkplain #maximumSize(long) maximumSize} or + * {@linkplain #maximumWeight(long) maximumWeight} is requested entries may be evicted on each cache + * modification. + * + *

If {@linkplain #expireAfterWrite expireAfterWrite} or + * {@linkplain #expireAfterAccess expireAfterAccess} is requested entries may be evicted on each + * cache modification, on occasional cache accesses, or on calls to {@link Cache#cleanUp}. Expired + * entries may be counted in {@link Cache#size}, but will never be visible to read or write + * operations. + * + *

If {@linkplain #weakKeys weakKeys}, {@linkplain #weakValues weakValues}, or + * {@linkplain #softValues softValues} are requested, it is possible for a key or value present in + * the cache to be reclaimed by the garbage collector. Entries with reclaimed keys or values may be + * removed from the cache on each cache modification, on occasional cache accesses, or on calls to + * {@link Cache#cleanUp}; such entries may be counted in {@link Cache#size}, but will never be + * visible to read or write operations. + * + *

Certain cache configurations will result in the accrual of periodic maintenance tasks which + * will be performed during write operations, or during occasional read operations in the absense of + * writes. The {@link Cache#cleanUp} method of the returned cache will also perform maintenance, but + * calling it should not be necessary with a high throughput cache. Only caches built with + * {@linkplain #removalListener removalListener}, {@linkplain #expireAfterWrite expireAfterWrite}, + * {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys weakKeys}, + * {@linkplain #weakValues weakValues}, or {@linkplain #softValues softValues} perform periodic + * maintenance. + * + *

The caches produced by {@code CacheBuilder} are serializable, and the deserialized caches + * retain all the configuration properties of the original cache. Note that the serialized form does + * not include cache contents, but only configuration. + * + *

See the Guava User Guide article on caching for a higher-level + * explanation. + * + * @param the base key type for all caches created by this builder + * @param the base value type for all caches created by this builder + * @author Charles Fry + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible(emulated = true) +public final class CacheBuilder { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 4; + private static final int DEFAULT_EXPIRATION_NANOS = 0; + private static final int DEFAULT_REFRESH_NANOS = 0; + + static final Supplier NULL_STATS_COUNTER = Suppliers.ofInstance( + new StatsCounter() { + @Override + public void recordHits(int count) {} + + @Override + public void recordMisses(int count) {} + + @Override + public void recordLoadSuccess(long loadTime) {} + + @Override + public void recordLoadException(long loadTime) {} + + @Override + public void recordEviction() {} + + @Override + public CacheStats snapshot() { + return EMPTY_STATS; + } + }); + static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0); + + static final Supplier CACHE_STATS_COUNTER = + new Supplier() { + @Override + public StatsCounter get() { + return new SimpleStatsCounter(); + } + }; + + enum NullListener implements RemovalListener { + INSTANCE; + + @Override + public void onRemoval(RemovalNotification notification) {} + } + + enum OneWeigher implements Weigher { + INSTANCE; + + @Override + public int weigh(Object key, Object value) { + return 1; + } + } + + static final Ticker NULL_TICKER = new Ticker() { + @Override + public long read() { + return 0; + } + }; + + private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName()); + + static final int UNSET_INT = -1; + + boolean strictParsing = true; + + int initialCapacity = UNSET_INT; + int concurrencyLevel = UNSET_INT; + long maximumSize = UNSET_INT; + long maximumWeight = UNSET_INT; + Weigher weigher; + + Strength keyStrength; + Strength valueStrength; + + long expireAfterWriteNanos = UNSET_INT; + long expireAfterAccessNanos = UNSET_INT; + long refreshNanos = UNSET_INT; + + Equivalence keyEquivalence; + Equivalence valueEquivalence; + + RemovalListener removalListener; + Ticker ticker; + + Supplier statsCounterSupplier = NULL_STATS_COUNTER; + + // TODO(fry): make constructor private and update tests to use newBuilder + CacheBuilder() {} + + /** + * Constructs a new {@code CacheBuilder} instance with default settings, including strong keys, + * strong values, and no automatic eviction of any kind. + */ + public static CacheBuilder newBuilder() { + return new CacheBuilder(); + } + + /** + * Constructs a new {@code CacheBuilder} instance with the settings specified in {@code spec}. + * + * @since 12.0 + */ + @Beta + @GwtIncompatible("To be supported") + public static CacheBuilder from(CacheBuilderSpec spec) { + return spec.toCacheBuilder() + .lenientParsing(); + } + + /** + * Constructs a new {@code CacheBuilder} instance with the settings specified in {@code spec}. + * This is especially useful for command-line configuration of a {@code CacheBuilder}. + * + * @param spec a String in the format specified by {@link CacheBuilderSpec} + * @since 12.0 + */ + @Beta + @GwtIncompatible("To be supported") + public static CacheBuilder from(String spec) { + return from(CacheBuilderSpec.parse(spec)); + } + + /** + * Enables lenient parsing. Useful for tests and spec parsing. + */ + CacheBuilder lenientParsing() { + strictParsing = false; + return this; + } + + /** + * Sets a custom {@code Equivalence} strategy for comparing keys. + * + *

By default, the cache uses {@link Equivalence#identity} to determine key equality when + * {@link #weakKeys} is specified, and {@link Equivalence#equals()} otherwise. + */ + CacheBuilder keyEquivalence(Equivalence equivalence) { + checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence); + keyEquivalence = checkNotNull(equivalence); + return this; + } + + Equivalence getKeyEquivalence() { + return firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence()); + } + + /** + * Sets a custom {@code Equivalence} strategy for comparing values. + * + *

By default, the cache uses {@link Equivalence#identity} to determine value equality when + * {@link #weakValues} or {@link #softValues} is specified, and {@link Equivalence#equals()} + * otherwise. + */ + CacheBuilder valueEquivalence(Equivalence equivalence) { + checkState(valueEquivalence == null, + "value equivalence was already set to %s", valueEquivalence); + this.valueEquivalence = checkNotNull(equivalence); + return this; + } + + Equivalence getValueEquivalence() { + return firstNonNull(valueEquivalence, getValueStrength().defaultEquivalence()); + } + + /** + * Sets the minimum total size for the internal hash tables. For example, if the initial capacity + * is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each + * having a hash table of size eight. Providing a large enough estimate at construction time + * avoids the need for expensive resizing operations later, but setting this value unnecessarily + * high wastes memory. + * + * @throws IllegalArgumentException if {@code initialCapacity} is negative + * @throws IllegalStateException if an initial capacity was already set + */ + public CacheBuilder initialCapacity(int initialCapacity) { + checkState(this.initialCapacity == UNSET_INT, "initial capacity was already set to %s", + this.initialCapacity); + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + int getInitialCapacity() { + return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; + } + + /** + * Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The + * table is internally partitioned to try to permit the indicated number of concurrent updates + * without contention. Because assignment of entries to these partitions is not necessarily + * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to + * accommodate as many threads as will ever concurrently modify the table. Using a significantly + * higher value than you need can waste space and time, and a significantly lower value can lead + * to thread contention. But overestimates and underestimates within an order of magnitude do not + * usually have much noticeable impact. A value of one permits only one thread to modify the cache + * at a time, but since read operations and cache loading computations can proceed concurrently, + * this still yields higher concurrency than full synchronization. + * + *

Defaults to 4. Note:The default may change in the future. If you care about this + * value, you should always choose it explicitly. + * + *

The current implementation uses the concurrency level to create a fixed number of hashtable + * segments, each governed by its own write lock. The segment lock is taken once for each explicit + * write, and twice for each cache loading computation (once prior to loading the new value, + * and once after loading completes). Much internal cache management is performed at the segment + * granularity. For example, access queues and write queues are kept per segment when they are + * required by the selected eviction algorithm. As such, when writing unit tests it is not + * uncommon to specify {@code concurrencyLevel(1)} in order to achieve more deterministic eviction + * behavior. + * + *

Note that future implementations may abandon segment locking in favor of more advanced + * concurrency controls. + * + * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive + * @throws IllegalStateException if a concurrency level was already set + */ + public CacheBuilder concurrencyLevel(int concurrencyLevel) { + checkState(this.concurrencyLevel == UNSET_INT, "concurrency level was already set to %s", + this.concurrencyLevel); + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + int getConcurrencyLevel() { + return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; + } + + /** + * Specifies the maximum number of entries the cache may contain. Note that the cache may evict + * an entry before this limit is exceeded. As the cache size grows close to the maximum, the + * cache evicts entries that are less likely to be used again. For example, the cache may evict an + * entry because it hasn't been used recently or very often. + * + *

When {@code size} is zero, elements will be evicted immediately after being loaded into the + * cache. This can be useful in testing, or to disable caching temporarily without a code change. + * + *

This feature cannot be used in conjunction with {@link #maximumWeight}. + * + * @param size the maximum size of the cache + * @throws IllegalArgumentException if {@code size} is negative + * @throws IllegalStateException if a maximum size or weight was already set + */ + public CacheBuilder maximumSize(long size) { + checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s", + this.maximumSize); + checkState(this.maximumWeight == UNSET_INT, "maximum weight was already set to %s", + this.maximumWeight); + checkState(this.weigher == null, "maximum size can not be combined with weigher"); + checkArgument(size >= 0, "maximum size must not be negative"); + this.maximumSize = size; + return this; + } + + /** + * Specifies the maximum weight of entries the cache may contain. Weight is determined using the + * {@link Weigher} specified with {@link #weigher}, and use of this method requires a + * corresponding call to {@link #weigher} prior to calling {@link #build}. + * + *

Note that the cache may evict an entry before this limit is exceeded. As the cache + * size grows close to the maximum, the cache evicts entries that are less likely to be used + * again. For example, the cache may evict an entry because it hasn't been used recently or very + * often. + * + *

When {@code weight} is zero, elements will be evicted immediately after being loaded into + * cache. This can be useful in testing, or to disable caching temporarily without a code + * change. + * + *

Note that weight is only used to determine whether the cache is over capacity; it has no + * effect on selecting which entry should be evicted next. + * + *

This feature cannot be used in conjunction with {@link #maximumSize}. + * + * @param weight the maximum total weight of entries the cache may contain + * @throws IllegalArgumentException if {@code weight} is negative + * @throws IllegalStateException if a maximum weight or size was already set + * @since 11.0 + */ + public CacheBuilder maximumWeight(long weight) { + checkState(this.maximumWeight == UNSET_INT, "maximum weight was already set to %s", + this.maximumWeight); + checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s", + this.maximumSize); + this.maximumWeight = weight; + checkArgument(weight >= 0, "maximum weight must not be negative"); + return this; + } + + /** + * Specifies the weigher to use in determining the weight of entries. Entry weight is taken + * into consideration by {@link #maximumWeight(long)} when determining which entries to evict, and + * use of this method requires a corresponding call to {@link #maximumWeight(long)} prior to + * calling {@link #build}. Weights are measured and recorded when entries are inserted into the + * cache, and are thus effectively static during the lifetime of a cache entry. + * + *

When the weight of an entry is zero it will not be considered for size-based eviction + * (though it still may be evicted by other means). + * + *

Important note: Instead of returning this as a {@code CacheBuilder} + * instance, this method returns {@code CacheBuilder}. From this point on, either the + * original reference or the returned reference may be used to complete configuration and build + * the cache, but only the "generic" one is type-safe. That is, it will properly prevent you from + * building caches whose key or value types are incompatible with the types accepted by the + * weigher already provided; the {@code CacheBuilder} type cannot do this. For best results, + * simply use the standard method-chaining idiom, as illustrated in the documentation at top, + * configuring a {@code CacheBuilder} and building your {@link Cache} all in a single statement. + * + *

Warning: if you ignore the above advice, and use this {@code CacheBuilder} to build + * a cache whose key or value type is incompatible with the weigher, you will likely experience + * a {@link ClassCastException} at some undefined point in the future. + * + * @param weigher the weigher to use in calculating the weight of cache entries + * @throws IllegalArgumentException if {@code size} is negative + * @throws IllegalStateException if a maximum size was already set + * @since 11.0 + */ + public CacheBuilder weigher( + Weigher weigher) { + checkState(this.weigher == null); + if (strictParsing) { + checkState(this.maximumSize == UNSET_INT, "weigher can not be combined with maximum size", + this.maximumSize); + } + + // safely limiting the kinds of caches this can produce + @SuppressWarnings("unchecked") + CacheBuilder me = (CacheBuilder) this; + me.weigher = checkNotNull(weigher); + return me; + } + + long getMaximumWeight() { + if (expireAfterWriteNanos == 0 || expireAfterAccessNanos == 0) { + return 0; + } + return (weigher == null) ? maximumSize : maximumWeight; + } + + // Make a safe contravariant cast now so we don't have to do it over and over. + @SuppressWarnings("unchecked") + Weigher getWeigher() { + return (Weigher) Objects.firstNonNull(weigher, OneWeigher.INSTANCE); + } + + /** + * Specifies that each key (not value) stored in the cache should be strongly referenced. + * + * @throws IllegalStateException if the key strength was already set + */ + CacheBuilder strongKeys() { + return setKeyStrength(Strength.STRONG); + } + + /** + * Specifies that each key (not value) stored in the cache should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Warning: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of keys. + * + *

Entries with keys that have been garbage collected may be counted in {@link Cache#size}, + * but will never be visible to read or write operations; such entries are cleaned up as part of + * the routine maintenance described in the class javadoc. + * + * @throws IllegalStateException if the key strength was already set + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public CacheBuilder weakKeys() { + return setKeyStrength(Strength.WEAK); + } + + CacheBuilder setKeyStrength(Strength strength) { + checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); + keyStrength = checkNotNull(strength); + return this; + } + + Strength getKeyStrength() { + return firstNonNull(keyStrength, Strength.STRONG); + } + + /** + * Specifies that each value (not key) stored in the cache should be strongly referenced. + * + * @throws IllegalStateException if the value strength was already set + */ + CacheBuilder strongValues() { + return setValueStrength(Strength.STRONG); + } + + /** + * Specifies that each value (not key) stored in the cache should be wrapped in a + * {@link WeakReference} (by default, strong references are used). + * + *

Weak values will be garbage collected once they are weakly reachable. This makes them a poor + * candidate for caching; consider {@link #softValues} instead. + * + *

Note: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of values. + * + *

Entries with values that have been garbage collected may be counted in {@link Cache#size}, + * but will never be visible to read or write operations; such entries are cleaned up as part of + * the routine maintenance described in the class javadoc. + * + * @throws IllegalStateException if the value strength was already set + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public CacheBuilder weakValues() { + return setValueStrength(Strength.WEAK); + } + + /** + * Specifies that each value (not key) stored in the cache should be wrapped in a + * {@link SoftReference} (by default, strong references are used). Softly-referenced objects will + * be garbage-collected in a globally least-recently-used manner, in response to memory + * demand. + * + *

Warning: in most circumstances it is better to set a per-cache {@linkplain + * #maximumSize(long) maximum size} instead of using soft references. You should only use this + * method if you are well familiar with the practical consequences of soft references. + * + *

Note: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of values. + * + *

Entries with values that have been garbage collected may be counted in {@link Cache#size}, + * but will never be visible to read or write operations; such entries are cleaned up as part of + * the routine maintenance described in the class javadoc. + * + * @throws IllegalStateException if the value strength was already set + */ + @GwtIncompatible("java.lang.ref.SoftReference") + public CacheBuilder softValues() { + return setValueStrength(Strength.SOFT); + } + + CacheBuilder setValueStrength(Strength strength) { + checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); + valueStrength = checkNotNull(strength); + return this; + } + + Strength getValueStrength() { + return firstNonNull(valueStrength, Strength.STRONG); + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, or the most recent replacement of its value. + * + *

When {@code duration} is zero, this method hands off to + * {@link #maximumSize(long) maximumSize}{@code (0)}, ignoring any otherwise-specificed maximum + * size or weight. This can be useful in testing, or to disable caching temporarily without a code + * change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + * @param duration the length of time after an entry is created that it should be automatically + * removed + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to live or time to idle was already set + */ + public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) { + checkState(expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns", + expireAfterWriteNanos); + checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); + this.expireAfterWriteNanos = unit.toNanos(duration); + return this; + } + + long getExpireAfterWriteNanos() { + return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, the most recent replacement of its value, or its last + * access. Access time is reset by all cache read and write operations (including + * {@code Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations + * on the collection-views of {@link Cache#asMap}. + * + *

When {@code duration} is zero, this method hands off to + * {@link #maximumSize(long) maximumSize}{@code (0)}, ignoring any otherwise-specificed maximum + * size or weight. This can be useful in testing, or to disable caching temporarily without a code + * change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + * @param duration the length of time after an entry is last accessed that it should be + * automatically removed + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to idle or time to live was already set + */ + public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) { + checkState(expireAfterAccessNanos == UNSET_INT, "expireAfterAccess was already set to %s ns", + expireAfterAccessNanos); + checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); + this.expireAfterAccessNanos = unit.toNanos(duration); + return this; + } + + long getExpireAfterAccessNanos() { + return (expireAfterAccessNanos == UNSET_INT) + ? DEFAULT_EXPIRATION_NANOS : expireAfterAccessNanos; + } + + /** + * Specifies that active entries are eligible for automatic refresh once a fixed duration has + * elapsed after the entry's creation, or the most recent replacement of its value. The semantics + * of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling + * {@link CacheLoader#reload}. + * + *

As the default implementation of {@link CacheLoader#reload} is synchronous, it is + * recommended that users of this method override {@link CacheLoader#reload} with an asynchronous + * implementation; otherwise refreshes will be performed during unrelated cache read and write + * operations. + * + *

Currently automatic refreshes are performed when the first stale request for an entry + * occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload} + * and immediately return the new value if the returned future is complete, and the old value + * otherwise. + * + *

Note: all exceptions thrown during refresh will be logged and then swallowed. + * + * @param duration the length of time after an entry is created that it should be considered + * stale, and thus eligible for refresh + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the refresh interval was already set + * @since 11.0 + */ + @Beta + @GwtIncompatible("To be supported") + public CacheBuilder refreshAfterWrite(long duration, TimeUnit unit) { + checkNotNull(unit); + checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos); + checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit); + this.refreshNanos = unit.toNanos(duration); + return this; + } + + long getRefreshNanos() { + return (refreshNanos == UNSET_INT) ? DEFAULT_REFRESH_NANOS : refreshNanos; + } + + /** + * Specifies a nanosecond-precision time source for use in determining when entries should be + * expired. By default, {@link System#nanoTime} is used. + * + *

The primary intent of this method is to facilitate testing of caches which have been + * configured with {@link #expireAfterWrite} or {@link #expireAfterAccess}. + * + * @throws IllegalStateException if a ticker was already set + */ + @GwtIncompatible("To be supported") + public CacheBuilder ticker(Ticker ticker) { + checkState(this.ticker == null); + this.ticker = checkNotNull(ticker); + return this; + } + + Ticker getTicker(boolean recordsTime) { + if (ticker != null) { + return ticker; + } + return recordsTime ? Ticker.systemTicker() : NULL_TICKER; + } + + /** + * Specifies a listener instance, which all caches built using this {@code CacheBuilder} will + * notify each time an entry is removed from the cache by any means. + * + *

Each cache built by this {@code CacheBuilder} after this method is called invokes the + * supplied listener after removing an element for any reason (see removal causes in {@link + * RemovalCause}). It will invoke the listener as part of the routine maintenance described + * in the class javadoc. + * + *

Note: all exceptions thrown by {@code listener} will be logged (using + * {@link java.util.logging.Logger})and then swallowed. + * + *

Important note: Instead of returning this as a {@code CacheBuilder} + * instance, this method returns {@code CacheBuilder}. From this point on, either the + * original reference or the returned reference may be used to complete configuration and build + * the cache, but only the "generic" one is type-safe. That is, it will properly prevent you from + * building caches whose key or value types are incompatible with the types accepted by the + * listener already provided; the {@code CacheBuilder} type cannot do this. For best results, + * simply use the standard method-chaining idiom, as illustrated in the documentation at top, + * configuring a {@code CacheBuilder} and building your {@link Cache} all in a single statement. + * + *

Warning: if you ignore the above advice, and use this {@code CacheBuilder} to build + * a cache whose key or value type is incompatible with the listener, you will likely experience + * a {@link ClassCastException} at some undefined point in the future. + * + * @throws IllegalStateException if a removal listener was already set + */ + @CheckReturnValue + @GwtIncompatible("To be supported") + public CacheBuilder removalListener( + RemovalListener listener) { + checkState(this.removalListener == null); + + // safely limiting the kinds of caches this can produce + @SuppressWarnings("unchecked") + CacheBuilder me = (CacheBuilder) this; + me.removalListener = checkNotNull(listener); + return me; + } + + // Make a safe contravariant cast now so we don't have to do it over and over. + @SuppressWarnings("unchecked") + RemovalListener getRemovalListener() { + return (RemovalListener) Objects.firstNonNull(removalListener, NullListener.INSTANCE); + } + + /** + * Enable the accumulation of {@link CacheStats} during the operation of the cache. Without this + * {@link Cache#stats} will return zero for all statistics. Note that recording stats requires + * bookkeeping to be performed with each operation, and thus imposes a performance penalty on + * cache operation. + * + * @since 12.0 (previously, stats collection was automatic) + */ + public CacheBuilder recordStats() { + statsCounterSupplier = CACHE_STATS_COUNTER; + return this; + } + + Supplier getStatsCounterSupplier() { + return statsCounterSupplier; + } + + /** + * Builds a cache, which either returns an already-loaded value for a given key or atomically + * computes or retrieves it using the supplied {@code CacheLoader}. If another thread is currently + * loading the value for this key, simply waits for that thread to finish and returns its + * loaded value. Note that multiple threads can concurrently load values for distinct keys. + * + *

This method does not alter the state of this {@code CacheBuilder} instance, so it can be + * invoked again to create multiple independent caches. + * + * @param loader the cache loader used to obtain new values + * @return a cache having the requested features + */ + public LoadingCache build( + CacheLoader loader) { + checkWeightWithWeigher(); + return new LocalCache.LocalLoadingCache(this, loader); + } + + /** + * Builds a cache which does not automatically load values when keys are requested. + * + *

Consider {@link #build(CacheLoader)} instead, if it is feasible to implement a + * {@code CacheLoader}. + * + *

This method does not alter the state of this {@code CacheBuilder} instance, so it can be + * invoked again to create multiple independent caches. + * + * @return a cache having the requested features + * @since 11.0 + */ + public Cache build() { + checkWeightWithWeigher(); + checkNonLoadingCache(); + return new LocalCache.LocalManualCache(this); + } + + private void checkNonLoadingCache() { + checkState(refreshNanos == UNSET_INT, "refreshAfterWrite requires a LoadingCache"); + } + + private void checkWeightWithWeigher() { + if (weigher == null) { + checkState(maximumWeight == UNSET_INT, "maximumWeight requires weigher"); + } else { + if (strictParsing) { + checkState(maximumWeight != UNSET_INT, "weigher requires maximumWeight"); + } else { + if (maximumWeight == UNSET_INT) { + logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight"); + } + } + } + } + + /** + * Returns a string representation for this CacheBuilder instance. The exact form of the returned + * string is not specified. + */ + @Override + public String toString() { + Objects.ToStringHelper s = Objects.toStringHelper(this); + if (initialCapacity != UNSET_INT) { + s.add("initialCapacity", initialCapacity); + } + if (concurrencyLevel != UNSET_INT) { + s.add("concurrencyLevel", concurrencyLevel); + } + if (maximumWeight != UNSET_INT) { + if (weigher == null) { + s.add("maximumSize", maximumWeight); + } else { + s.add("maximumWeight", maximumWeight); + } + } + if (expireAfterWriteNanos != UNSET_INT) { + s.add("expireAfterWrite", expireAfterWriteNanos + "ns"); + } + if (expireAfterAccessNanos != UNSET_INT) { + s.add("expireAfterAccess", expireAfterAccessNanos + "ns"); + } + if (keyStrength != null) { + s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString())); + } + if (valueStrength != null) { + s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString())); + } + if (keyEquivalence != null) { + s.addValue("keyEquivalence"); + } + if (valueEquivalence != null) { + s.addValue("valueEquivalence"); + } + if (removalListener != null) { + s.addValue("removalListener"); + } + return s.toString(); + } +} diff --git a/guava/src/com/google/common/cache/CacheBuilderSpec.java b/guava/src/com/google/common/cache/CacheBuilderSpec.java new file mode 100644 index 0000000..9dfaf4d --- /dev/null +++ b/guava/src/com/google/common/cache/CacheBuilderSpec.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.cache.LocalCache.Strength; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +/** + * A specification of a {@link CacheBuilder} configuration. + * + *

{@code CacheBuilderSpec} supports parsing configuration off of a string, which + * makes it especially useful for command-line configuration of a {@code CacheBuilder}. + * + *

The string syntax is a series of comma-separated keys or key-value pairs, + * each corresponding to a {@code CacheBuilder} method. + *

    + *
  • {@code concurrencyLevel=[integer]}: sets {@link CacheBuilder#concurrencyLevel}. + *
  • {@code initialCapacity=[integer]}: sets {@link CacheBuilder#initialCapacity}. + *
  • {@code maximumSize=[long]}: sets {@link CacheBuilder#maximumSize}. + *
  • {@code maximumWeight=[long]}: sets {@link CacheBuilder#maximumWeight}. + *
  • {@code expireAfterAccess=[duration]}: sets {@link CacheBuilder#expireAfterAccess}. + *
  • {@code expireAfterWrite=[duration]}: sets {@link CacheBuilder#expireAfterWrite}. + *
  • {@code refreshAfterWrite=[duration]}: sets {@link CacheBuilder#refreshAfterWrite}. + *
  • {@code weakKeys}: sets {@link CacheBuilder#weakKeys}. + *
  • {@code softValues}: sets {@link CacheBuilder#softValues}. + *
  • {@code weakValues}: sets {@link CacheBuilder#weakValues}. + *
+ * + * The set of supported keys will grow as {@code CacheBuilder} evolves, but existing keys + * will never be removed. + * + *

Durations are represented by an integer, followed by one of "d", "h", "m", + * or "s", representing days, hours, minutes, or seconds respectively. (There + * is currently no syntax to request expiration in milliseconds, microseconds, + * or nanoseconds.) + * + *

Whitespace before and after commas and equal signs is ignored. Keys may + * not be repeated; it is also illegal to use the following pairs of keys in + * a single value: + *

    + *
  • {@code maximumSize} and {@code maximumWeight} + *
  • {@code softValues} and {@code weakValues} + *
+ * + *

{@code CacheBuilderSpec} does not support configuring {@code CacheBuilder} methods + * with non-value parameters. These must be configured in code. + * + *

A new {@code CacheBuilder} can be instantiated from a {@code CacheBuilderSpec} using + * {@link CacheBuilder#from(CacheBuilderSpec)} or {@link CacheBuilder#from(String)}. + * + * @author Adam Winer + * @since 12.0 + */ +@Beta +public final class CacheBuilderSpec { + /** Parses a single value. */ + private interface ValueParser { + void parse(CacheBuilderSpec spec, String key, @Nullable String value); + } + + /** Splits each key-value pair. */ + private static final Splitter KEYS_SPLITTER = Splitter.on(',').trimResults(); + + /** Splits the key from the value. */ + private static final Splitter KEY_VALUE_SPLITTER = Splitter.on('=').trimResults(); + + /** Map of names to ValueParser. */ + private static final ImmutableMap VALUE_PARSERS = + ImmutableMap.builder() + .put("initialCapacity", new InitialCapacityParser()) + .put("maximumSize", new MaximumSizeParser()) + .put("maximumWeight", new MaximumWeightParser()) + .put("concurrencyLevel", new ConcurrencyLevelParser()) + .put("weakKeys", new KeyStrengthParser(Strength.WEAK)) + .put("softValues", new ValueStrengthParser(Strength.SOFT)) + .put("weakValues", new ValueStrengthParser(Strength.WEAK)) + .put("expireAfterAccess", new AccessDurationParser()) + .put("expireAfterWrite", new WriteDurationParser()) + .put("refreshAfterWrite", new RefreshDurationParser()) + .put("refreshInterval", new RefreshDurationParser()) + .build(); + + @VisibleForTesting Integer initialCapacity; + @VisibleForTesting Long maximumSize; + @VisibleForTesting Long maximumWeight; + @VisibleForTesting Integer concurrencyLevel; + @VisibleForTesting Strength keyStrength; + @VisibleForTesting Strength valueStrength; + @VisibleForTesting long writeExpirationDuration; + @VisibleForTesting TimeUnit writeExpirationTimeUnit; + @VisibleForTesting long accessExpirationDuration; + @VisibleForTesting TimeUnit accessExpirationTimeUnit; + @VisibleForTesting long refreshDuration; + @VisibleForTesting TimeUnit refreshTimeUnit; + /** Specification; used for toParseableString(). */ + private final String specification; + + private CacheBuilderSpec(String specification) { + this.specification = specification; + } + + /** + * Creates a CacheBuilderSpec from a string. + * + * @param cacheBuilderSpecification the string form + */ + public static CacheBuilderSpec parse(String cacheBuilderSpecification) { + CacheBuilderSpec spec = new CacheBuilderSpec(cacheBuilderSpecification); + if (!cacheBuilderSpecification.isEmpty()) { + for (String keyValuePair : KEYS_SPLITTER.split(cacheBuilderSpecification)) { + List keyAndValue = ImmutableList.copyOf(KEY_VALUE_SPLITTER.split(keyValuePair)); + checkArgument(!keyAndValue.isEmpty(), "blank key-value pair"); + checkArgument(keyAndValue.size() <= 2, + "key-value pair %s with more than one equals sign", keyValuePair); + + // Find the ValueParser for the current key. + String key = keyAndValue.get(0); + ValueParser valueParser = VALUE_PARSERS.get(key); + checkArgument(valueParser != null, "unknown key %s", key); + + String value = keyAndValue.size() == 1 ? null : keyAndValue.get(1); + valueParser.parse(spec, key, value); + } + } + + return spec; + } + + /** + * Returns a CacheBuilderSpec that will prevent caching. + */ + public static CacheBuilderSpec disableCaching() { + // Maximum size of zero is one way to block caching + return CacheBuilderSpec.parse("maximumSize=0"); + } + + /** + * Returns a CacheBuilder configured according to this instance's specification. + */ + CacheBuilder toCacheBuilder() { + CacheBuilder builder = CacheBuilder.newBuilder(); + if (initialCapacity != null) { + builder.initialCapacity(initialCapacity); + } + if (maximumSize != null) { + builder.maximumSize(maximumSize); + } + if (maximumWeight != null) { + builder.maximumWeight(maximumWeight); + } + if (concurrencyLevel != null) { + builder.concurrencyLevel(concurrencyLevel); + } + if (keyStrength != null) { + switch (keyStrength) { + case WEAK: + builder.weakKeys(); + break; + default: + throw new AssertionError(); + } + } + if (valueStrength != null) { + switch (valueStrength) { + case SOFT: + builder.softValues(); + break; + case WEAK: + builder.weakValues(); + break; + default: + throw new AssertionError(); + } + } + if (writeExpirationTimeUnit != null) { + builder.expireAfterWrite(writeExpirationDuration, writeExpirationTimeUnit); + } + if (accessExpirationTimeUnit != null) { + builder.expireAfterAccess(accessExpirationDuration, accessExpirationTimeUnit); + } + if (refreshTimeUnit != null) { + builder.refreshAfterWrite(refreshDuration, refreshTimeUnit); + } + + return builder; + } + + /** + * Returns a string that can be used to parse an equivalent + * {@code CacheBuilderSpec}. The order and form of this representation is + * not guaranteed, except that reparsing its output will produce + * a {@code CacheBuilderSpec} equal to this instance. + */ + public String toParsableString() { + return specification; + } + + /** + * Returns a string representation for this CacheBuilderSpec instance. + * The form of this representation is not guaranteed. + */ + @Override + public String toString() { + return Objects.toStringHelper(this).addValue(toParsableString()).toString(); + } + + @Override + public int hashCode() { + return Objects.hashCode( + initialCapacity, + maximumSize, + maximumWeight, + concurrencyLevel, + keyStrength, + valueStrength, + durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), + durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), + durationInNanos(refreshDuration, refreshTimeUnit)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CacheBuilderSpec)) { + return false; + } + CacheBuilderSpec that = (CacheBuilderSpec) obj; + return Objects.equal(initialCapacity, that.initialCapacity) + && Objects.equal(maximumSize, that.maximumSize) + && Objects.equal(maximumWeight, that.maximumWeight) + && Objects.equal(concurrencyLevel, that.concurrencyLevel) + && Objects.equal(keyStrength, that.keyStrength) + && Objects.equal(valueStrength, that.valueStrength) + && Objects.equal(durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), + durationInNanos(that.writeExpirationDuration, that.writeExpirationTimeUnit)) + && Objects.equal(durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), + durationInNanos(that.accessExpirationDuration, that.accessExpirationTimeUnit)) + && Objects.equal(durationInNanos(refreshDuration, refreshTimeUnit), + durationInNanos(that.refreshDuration, that.refreshTimeUnit)); + } + + /** + * Converts an expiration duration/unit pair into a single Long for hashing and equality. + * Uses nanos to match CacheBuilder implementation. + */ + @Nullable private static Long durationInNanos(long duration, @Nullable TimeUnit unit) { + return (unit == null) ? null : unit.toNanos(duration); + } + + /** Base class for parsing integers. */ + abstract static class IntegerParser implements ValueParser { + protected abstract void parseInteger(CacheBuilderSpec spec, int value); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + parseInteger(spec, Integer.parseInt(value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + String.format("key %s value set to %s, must be integer", key, value), e); + } + } + } + + /** Base class for parsing integers. */ + abstract static class LongParser implements ValueParser { + protected abstract void parseLong(CacheBuilderSpec spec, long value); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + parseLong(spec, Long.parseLong(value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + String.format("key %s value set to %s, must be integer", key, value), e); + } + } + } + + /** Parse initialCapacity */ + static class InitialCapacityParser extends IntegerParser { + @Override + protected void parseInteger(CacheBuilderSpec spec, int value) { + checkArgument(spec.initialCapacity == null, + "initial capacity was already set to ", spec.initialCapacity); + spec.initialCapacity = value; + } + } + + /** Parse maximumSize */ + static class MaximumSizeParser extends LongParser { + @Override + protected void parseLong(CacheBuilderSpec spec, long value) { + checkArgument(spec.maximumSize == null, + "maximum size was already set to ", spec.maximumSize); + checkArgument(spec.maximumWeight == null, + "maximum weight was already set to ", spec.maximumWeight); + spec.maximumSize = value; + } + } + + /** Parse maximumWeight */ + static class MaximumWeightParser extends LongParser { + @Override + protected void parseLong(CacheBuilderSpec spec, long value) { + checkArgument(spec.maximumWeight == null, + "maximum weight was already set to ", spec.maximumWeight); + checkArgument(spec.maximumSize == null, + "maximum size was already set to ", spec.maximumSize); + spec.maximumWeight = value; + } + } + + /** Parse concurrencyLevel */ + static class ConcurrencyLevelParser extends IntegerParser { + @Override + protected void parseInteger(CacheBuilderSpec spec, int value) { + checkArgument(spec.concurrencyLevel == null, + "concurrency level was already set to ", spec.concurrencyLevel); + spec.concurrencyLevel = value; + } + } + + /** Parse weakKeys */ + static class KeyStrengthParser implements ValueParser { + private final Strength strength; + + public KeyStrengthParser(Strength strength) { + this.strength = strength; + } + + @Override + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { + checkArgument(value == null, "key %s does not take values", key); + checkArgument(spec.keyStrength == null, "%s was already set to %s", key, spec.keyStrength); + spec.keyStrength = strength; + } + } + + /** Parse weakValues and softValues */ + static class ValueStrengthParser implements ValueParser { + private final Strength strength; + + public ValueStrengthParser(Strength strength) { + this.strength = strength; + } + + @Override + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { + checkArgument(value == null, "key %s does not take values", key); + checkArgument(spec.valueStrength == null, + "%s was already set to %s", key, spec.valueStrength); + + spec.valueStrength = strength; + } + } + + /** Base class for parsing times with durations */ + abstract static class DurationParser implements ValueParser { + protected abstract void parseDuration( + CacheBuilderSpec spec, + long duration, + TimeUnit unit); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + char lastChar = value.charAt(value.length() - 1); + TimeUnit timeUnit; + switch (lastChar) { + case 'd': + timeUnit = TimeUnit.DAYS; + break; + case 'h': + timeUnit = TimeUnit.HOURS; + break; + case 'm': + timeUnit = TimeUnit.MINUTES; + break; + case 's': + timeUnit = TimeUnit.SECONDS; + break; + default: + throw new IllegalArgumentException( + String.format("key %s invalid format. was %s, must end with one of [dDhHmMsS]", + key, value)); + } + + long duration = Long.parseLong(value.substring(0, value.length() - 1)); + parseDuration(spec, duration, timeUnit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + String.format("key %s value set to %s, must be integer", key, value)); + } + } + } + + /** Parse expireAfterAccess */ + static class AccessDurationParser extends DurationParser { + @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.accessExpirationTimeUnit == null, "expireAfterAccess already set"); + spec.accessExpirationDuration = duration; + spec.accessExpirationTimeUnit = unit; + } + } + + /** Parse expireAfterWrite */ + static class WriteDurationParser extends DurationParser { + @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.writeExpirationTimeUnit == null, "expireAfterWrite already set"); + spec.writeExpirationDuration = duration; + spec.writeExpirationTimeUnit = unit; + } + } + + /** Parse refreshAfterWrite */ + static class RefreshDurationParser extends DurationParser { + @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.refreshTimeUnit == null, "refreshAfterWrite already set"); + spec.refreshDuration = duration; + spec.refreshTimeUnit = unit; + } + } +} diff --git a/guava/src/com/google/common/cache/CacheLoader.java b/guava/src/com/google/common/cache/CacheLoader.java new file mode 100644 index 0000000..5f77001 --- /dev/null +++ b/guava/src/com/google/common/cache/CacheLoader.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.Serializable; +import java.util.Map; + +/** + * Computes or retrieves values, based on a key, for use in populating a {@link LoadingCache}. + * + *

Most implementations will only need to implement {@link #load}. Other methods may be + * overridden as desired. + * + *

Usage example:

   {@code
+ *
+ *   CacheLoader loader = new CacheLoader() {
+ *     public Graph load(Key key) throws AnyException {
+ *       return createExpensiveGraph(key);
+ *     }
+ *   };
+ *   LoadingCache cache = CacheBuilder.newBuilder().build(loader);}
+ * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible(emulated = true) +public abstract class CacheLoader { + /** + * Constructor for use by subclasses. + */ + protected CacheLoader() {} + + /** + * Computes or retrieves the value corresponding to {@code key}. + * + * @param key the non-null key whose value should be loaded + * @return the value associated with {@code key}; must not be null + * @throws Exception if unable to load the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + */ + public abstract V load(K key) throws Exception; + + /** + * Computes or retrieves a replacement value corresponding to an already-cached {@code key}. This + * method is called when an existing cache entry is refreshed by + * {@link CacheBuilder#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}. + * + *

This implementation synchronously delegates to {@link #load}. It is recommended that it be + * overridden with an asynchronous implementation when using + * {@link CacheBuilder#refreshAfterWrite}. + * + *

Note: all exceptions thrown by this method will be logged and then swallowed. + * + * @param key the non-null key whose value should be loaded + * @param oldValue the non-null old value corresponding to {@code key} + * @return the future new value associated with {@code key}; + * must not be null, must not return null + * @throws Exception if unable to reload the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + * @since 11.0 + */ + @GwtIncompatible("Futures") + public ListenableFuture reload(K key, V oldValue) throws Exception { + return Futures.immediateFuture(load(key)); + } + + /** + * Computes or retrieves the values corresponding to {@code keys}. This method is called by + * {@link LoadingCache#getAll}. + * + *

If the returned map doesn't contain all requested {@code keys} then the entries it does + * contain will be cached, but {@code getAll} will throw an exception. If the returned map + * contains extra keys not present in {@code keys} then all returned entries will be cached, + * but only the entries for {@code keys} will be returned from {@code getAll}. + * + *

This method should be overriden when bulk retrieval is significantly more efficient than + * many individual lookups. Note that {@link LoadingCache#getAll} will defer to individual calls + * to {@link LoadingCache#get} if this method is not overriden. + * + * @param keys the unique, non-null keys whose values should be loaded + * @return a map from each key in {@code keys} to the value associated with that key; + * may not contain null values + * @throws Exception if unable to load the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + * @since 11.0 + */ + public Map loadAll(Iterable keys) throws Exception { + // This will be caught by getAll(), causing it to fall back to multiple calls to + // LoadingCache.get + throw new UnsupportedLoadingOperationException(); + } + + /** + * Returns a cache loader based on an existing function instance. Note that there's no need + * to create a new function just to pass it in here; just subclass {@code CacheLoader} and + * implement {@link #load load} instead. + * + * @param function the function to be used for loading values; must never return {@code null} + * @return a cache loader that loads values by passing each key to {@code function} + */ + @Beta + public static CacheLoader from(Function function) { + return new FunctionToCacheLoader(function); + } + + private static final class FunctionToCacheLoader + extends CacheLoader implements Serializable { + private final Function computingFunction; + + public FunctionToCacheLoader(Function computingFunction) { + this.computingFunction = checkNotNull(computingFunction); + } + + @Override + public V load(K key) { + return computingFunction.apply(key); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a cache loader based on an existing supplier instance. Note that there's no need + * to create a new supplier just to pass it in here; just subclass {@code CacheLoader} and + * implement {@link #load load} instead. + * + * @param supplier the supplier to be used for loading values; must never return {@code null} + * @return a cache loader that loads values by calling {@link Supplier#get}, irrespective of the + * key + */ + @Beta + public static CacheLoader from(Supplier supplier) { + return new SupplierToCacheLoader(supplier); + } + + private static final class SupplierToCacheLoader + extends CacheLoader implements Serializable { + private final Supplier computingSupplier; + + public SupplierToCacheLoader(Supplier computingSupplier) { + this.computingSupplier = checkNotNull(computingSupplier); + } + + @Override + public V load(Object key) { + return computingSupplier.get(); + } + + private static final long serialVersionUID = 0; + } + + static final class UnsupportedLoadingOperationException extends UnsupportedOperationException {} + + /** + * Thrown to indicate that an invalid response was returned from a call to {@link CacheLoader}. + * + * @since 11.0 + */ + public static final class InvalidCacheLoadException extends RuntimeException { + public InvalidCacheLoadException(String message) { + super(message); + } + } +} diff --git a/guava/src/com/google/common/cache/CacheStats.java b/guava/src/com/google/common/cache/CacheStats.java new file mode 100644 index 0000000..04d442c --- /dev/null +++ b/guava/src/com/google/common/cache/CacheStats.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import javax.annotation.Nullable; + +/** + * Statistics about the performance of a {@link Cache}. Instances of this class are immutable. + * + *

Cache statistics are incremented according to the following rules: + * + *

    + *
  • When a cache lookup encounters an existing cache entry {@code hitCount} is incremented. + *
  • When a cache lookup first encounters a missing cache entry, a new entry is loaded. + *
      + *
    • After successfully loading an entry {@code missCount} and {@code loadSuccessCount} are + * incremented, and the total loading time, in nanoseconds, is added to + * {@code totalLoadTime}. + *
    • When an exception is thrown while loading an entry, {@code missCount} and {@code + * loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is + * added to {@code totalLoadTime}. + *
    • Cache lookups that encounter a missing cache entry that is still loading will wait + * for loading to complete (whether successful or not) and then increment {@code missCount}. + *
    + *
  • When an entry is evicted from the cache, {@code evictionCount} is incremented. + *
  • No stats are modified when a cache entry is invalidated or manually removed. + *
  • No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of + * the cache. + *
+ * + * @author Charles Fry + * @since 10.0 + */ +@Beta +@GwtCompatible +public final class CacheStats { + private final long hitCount; + private final long missCount; + private final long loadSuccessCount; + private final long loadExceptionCount; + private final long totalLoadTime; + private final long evictionCount; + + /** + * Constructs a new {@code CacheStats} instance. + * + *

Five parameters of the same type in a row is a bad thing, but this class is not constructed + * by end users and is too fine-grained for a builder. + */ + public CacheStats(long hitCount, long missCount, long loadSuccessCount, + long loadExceptionCount, long totalLoadTime, long evictionCount) { + checkArgument(hitCount >= 0); + checkArgument(missCount >= 0); + checkArgument(loadSuccessCount >= 0); + checkArgument(loadExceptionCount >= 0); + checkArgument(totalLoadTime >= 0); + checkArgument(evictionCount >= 0); + + this.hitCount = hitCount; + this.missCount = missCount; + this.loadSuccessCount = loadSuccessCount; + this.loadExceptionCount = loadExceptionCount; + this.totalLoadTime = totalLoadTime; + this.evictionCount = evictionCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have returned either a cached or + * uncached value. This is defined as {@code hitCount + missCount}. + */ + public long requestCount() { + return hitCount + missCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have returned a cached value. + */ + public long hitCount() { + return hitCount; + } + + /** + * Returns the ratio of cache requests which were hits. This is defined as + * {@code hitCount / requestCount}, or {@code 1.0} when {@code requestCount == 0}. + * Note that {@code hitRate + missRate =~ 1.0}. + */ + public double hitRate() { + long requestCount = requestCount(); + return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have returned an uncached (newly + * loaded) value, or null. Multiple concurrent calls to {@link Cache} lookup methods on an absent + * value can result in multiple misses, all returning the results of a single cache load + * operation. + */ + public long missCount() { + return missCount; + } + + /** + * Returns the ratio of cache requests which were misses. This is defined as + * {@code missCount / requestCount}, or {@code 0.0} when {@code requestCount == 0}. + * Note that {@code hitRate + missRate =~ 1.0}. Cache misses include all requests which + * weren't cache hits, including requests which resulted in either successful or failed loading + * attempts, and requests which waited for other threads to finish loading. It is thus the case + * that {@code missCount >= loadSuccessCount + loadExceptionCount}. Multiple + * concurrent misses for the same key will result in a single load operation. + */ + public double missRate() { + long requestCount = requestCount(); + return (requestCount == 0) ? 0.0 : (double) missCount / requestCount; + } + + /** + * Returns the total number of times that {@link Cache} lookup methods attempted to load new + * values. This includes both successful load operations, as well as those that threw + * exceptions. This is defined as {@code loadSuccessCount + loadExceptionCount}. + */ + public long loadCount() { + return loadSuccessCount + loadExceptionCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have successfully loaded a new value. + * This is always incremented in conjunction with {@link #missCount}, though {@code missCount} + * is also incremented when an exception is encountered during cache loading (see + * {@link #loadExceptionCount}). Multiple concurrent misses for the same key will result in a + * single load operation. + */ + public long loadSuccessCount() { + return loadSuccessCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods threw an exception while loading a + * new value. This is always incremented in conjunction with {@code missCount}, though + * {@code missCount} is also incremented when cache loading completes successfully (see + * {@link #loadSuccessCount}). Multiple concurrent misses for the same key will result in a + * single load operation. + */ + public long loadExceptionCount() { + return loadExceptionCount; + } + + /** + * Returns the ratio of cache loading attempts which threw exceptions. This is defined as + * {@code loadExceptionCount / (loadSuccessCount + loadExceptionCount)}, or + * {@code 0.0} when {@code loadSuccessCount + loadExceptionCount == 0}. + */ + public double loadExceptionRate() { + long totalLoadCount = loadSuccessCount + loadExceptionCount; + return (totalLoadCount == 0) + ? 0.0 + : (double) loadExceptionCount / totalLoadCount; + } + + /** + * Returns the total number of nanoseconds the cache has spent loading new values. This can be + * used to calculate the miss penalty. This value is increased every time + * {@code loadSuccessCount} or {@code loadExceptionCount} is incremented. + */ + public long totalLoadTime() { + return totalLoadTime; + } + + /** + * Returns the average time spent loading new values. This is defined as + * {@code totalLoadTime / (loadSuccessCount + loadExceptionCount)}. + */ + public double averageLoadPenalty() { + long totalLoadCount = loadSuccessCount + loadExceptionCount; + return (totalLoadCount == 0) + ? 0.0 + : (double) totalLoadTime / totalLoadCount; + } + + /** + * Returns the number of times an entry has been evicted. This count does not include manual + * {@linkplain Cache#invalidate invalidations}. + */ + public long evictionCount() { + return evictionCount; + } + + /** + * Returns a new {@code CacheStats} representing the difference between this {@code CacheStats} + * and {@code other}. Negative values, which aren't supported by {@code CacheStats} will be + * rounded up to zero. + */ + public CacheStats minus(CacheStats other) { + return new CacheStats( + Math.max(0, hitCount - other.hitCount), + Math.max(0, missCount - other.missCount), + Math.max(0, loadSuccessCount - other.loadSuccessCount), + Math.max(0, loadExceptionCount - other.loadExceptionCount), + Math.max(0, totalLoadTime - other.totalLoadTime), + Math.max(0, evictionCount - other.evictionCount)); + } + + /** + * Returns a new {@code CacheStats} representing the sum of this {@code CacheStats} + * and {@code other}. + * + * @since 11.0 + */ + public CacheStats plus(CacheStats other) { + return new CacheStats( + hitCount + other.hitCount, + missCount + other.missCount, + loadSuccessCount + other.loadSuccessCount, + loadExceptionCount + other.loadExceptionCount, + totalLoadTime + other.totalLoadTime, + evictionCount + other.evictionCount); + } + + @Override + public int hashCode() { + return Objects.hashCode(hitCount, missCount, loadSuccessCount, loadExceptionCount, + totalLoadTime, evictionCount); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof CacheStats) { + CacheStats other = (CacheStats) object; + return hitCount == other.hitCount + && missCount == other.missCount + && loadSuccessCount == other.loadSuccessCount + && loadExceptionCount == other.loadExceptionCount + && totalLoadTime == other.totalLoadTime + && evictionCount == other.evictionCount; + } + return false; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("hitCount", hitCount) + .add("missCount", missCount) + .add("loadSuccessCount", loadSuccessCount) + .add("loadExceptionCount", loadExceptionCount) + .add("totalLoadTime", totalLoadTime) + .add("evictionCount", evictionCount) + .toString(); + } +} diff --git a/guava/src/com/google/common/cache/ForwardingCache.java b/guava/src/com/google/common/cache/ForwardingCache.java new file mode 100644 index 0000000..44fe683 --- /dev/null +++ b/guava/src/com/google/common/cache/ForwardingCache.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingObject; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +import javax.annotation.Nullable; + +/** + * A cache which forwards all its method calls to another cache. Subclasses should override one or + * more methods to modify the behavior of the backing cache as desired per the + * decorator pattern. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +public abstract class ForwardingCache extends ForwardingObject implements Cache { + + /** Constructor for use by subclasses. */ + protected ForwardingCache() {} + + @Override + protected abstract Cache delegate(); + + /** + * @since 11.0 + */ + @Override + @Nullable + public V getIfPresent(Object key) { + return delegate().getIfPresent(key); + } + + /** + * @since 11.0 + */ + @Override + public V get(K key, Callable valueLoader) throws ExecutionException { + return delegate().get(key, valueLoader); + } + + /** + * @since 11.0 + */ + @Override + public ImmutableMap getAllPresent(Iterable keys) { + return delegate().getAllPresent(keys); + } + + /** + * @since 11.0 + */ + @Override + public void put(K key, V value) { + delegate().put(key, value); + } + + /** + * @since 12.0 + */ + @Override + public void putAll(Map m) { + delegate().putAll(m); + } + + @Override + public void invalidate(Object key) { + delegate().invalidate(key); + } + + /** + * @since 11.0 + */ + @Override + public void invalidateAll(Iterable keys) { + delegate().invalidateAll(keys); + } + + @Override + public void invalidateAll() { + delegate().invalidateAll(); + } + + @Override + public long size() { + return delegate().size(); + } + + @Override + public CacheStats stats() { + return delegate().stats(); + } + + @Override + public ConcurrentMap asMap() { + return delegate().asMap(); + } + + @Override + public void cleanUp() { + delegate().cleanUp(); + } + + /** + * A simplified version of {@link ForwardingCache} where subclasses can pass in an already + * constructed {@link Cache} as the delegete. + * + * @since 10.0 + */ + @Beta + public abstract static class SimpleForwardingCache extends ForwardingCache { + private final Cache delegate; + + protected SimpleForwardingCache(Cache delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final Cache delegate() { + return delegate; + } + } +} diff --git a/guava/src/com/google/common/cache/ForwardingLoadingCache.java b/guava/src/com/google/common/cache/ForwardingLoadingCache.java new file mode 100644 index 0000000..93732ff --- /dev/null +++ b/guava/src/com/google/common/cache/ForwardingLoadingCache.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; + +import java.util.concurrent.ExecutionException; + +/** + * A cache which forwards all its method calls to another cache. Subclasses should override one or + * more methods to modify the behavior of the backing cache as desired per the + * decorator pattern. + * + *

Note that {@link #get}, {@link #getUnchecked}, and {@link #apply} all expose the same + * underlying functionality, so should probably be overridden as a group. + * + * @author Charles Fry + * @since 11.0 + */ +@Beta +public abstract class ForwardingLoadingCache + extends ForwardingCache implements LoadingCache { + + /** Constructor for use by subclasses. */ + protected ForwardingLoadingCache() {} + + @Override + protected abstract LoadingCache delegate(); + + @Override + public V get(K key) throws ExecutionException { + return delegate().get(key); + } + + @Override + public V getUnchecked(K key) { + return delegate().getUnchecked(key); + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return delegate().getAll(keys); + } + + @Override + public V apply(K key) { + return delegate().apply(key); + } + + @Override + public void refresh(K key) { + delegate().refresh(key); + } + + /** + * A simplified version of {@link ForwardingLoadingCache} where subclasses can pass in an already + * constructed {@link LoadingCache} as the delegete. + * + * @since 10.0 + */ + @Beta + public abstract static class SimpleForwardingLoadingCache + extends ForwardingLoadingCache { + private final LoadingCache delegate; + + protected SimpleForwardingLoadingCache(LoadingCache delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final LoadingCache delegate() { + return delegate; + } + } +} diff --git a/guava/src/com/google/common/cache/LoadingCache.java b/guava/src/com/google/common/cache/LoadingCache.java new file mode 100644 index 0000000..05b1312 --- /dev/null +++ b/guava/src/com/google/common/cache/LoadingCache.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +/** + * A semi-persistent mapping from keys to values. Values are automatically loaded by the cache, + * and are stored in the cache until either evicted or manually invalidated. + * + *

Implementations of this interface are expected to be thread-safe, and can be safely accessed + * by multiple concurrent threads. + * + *

When evaluated as a {@link Function}, a cache yields the same result as invoking + * {@link #getUnchecked}. + * + *

Note that while this class is still annotated as {@link Beta}, the API is frozen from a + * consumer's standpoint. In other words existing methods are all considered {@code non-Beta} and + * won't be changed without going through an 18 month deprecation cycle; however new methods may be + * added at any time. + * + * @author Charles Fry + * @since 11.0 + */ +@Beta +@GwtCompatible +public interface LoadingCache extends Cache, Function { + + /** + * Returns the value associated with {@code key} in this cache, first loading that value if + * necessary. No observable state associated with this cache is modified until loading completes. + * + *

If another call to {@link #get} or {@link #getUnchecked} is currently loading the value for + * {@code key}, simply waits for that thread to finish and returns its loaded value. Note that + * multiple threads can concurrently load values for distinct keys. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#load} to load new values + * into the cache. Newly loaded values are added to the cache using + * {@code Cache.asMap().putIfAbsent} after loading has completed; if another value was associated + * with {@code key} while the new value was loading then a removal notification will be sent for + * the new value. + * + *

If the cache loader associated with this cache is known not to throw checked + * exceptions, then prefer {@link #getUnchecked} over this method. + * + * @throws ExecutionException if a checked exception was thrown while loading the value + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * value + * @throws ExecutionError if an error was thrown while loading the value + */ + V get(K key) throws ExecutionException; + + /** + * Returns the value associated with {@code key} in this cache, first loading that value if + * necessary. No observable state associated with this cache is modified until loading + * completes. Unlike {@link #get}, this method does not throw a checked exception, and thus should + * only be used in situations where checked exceptions are not thrown by the cache loader. + * + *

If another call to {@link #get} or {@link #getUnchecked} is currently loading the value for + * {@code key}, simply waits for that thread to finish and returns its loaded value. Note that + * multiple threads can concurrently load values for distinct keys. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#load} to load new values + * into the cache. Newly loaded values are added to the cache using + * {@code Cache.asMap().putIfAbsent} after loading has completed; if another value was associated + * with {@code key} while the new value was loading then a removal notification will be sent for + * the new value. + * + *

Warning: this method silently converts checked exceptions to unchecked exceptions, + * and should not be used with cache loaders which throw checked exceptions. In such cases use + * {@link #get} instead. + * + * @throws UncheckedExecutionException if an exception was thrown while loading the value, + * regardless of whether the exception was checked or unchecked + * @throws ExecutionError if an error was thrown while loading the value + */ + V getUnchecked(K key); + + /** + * Returns a map of the values associated with {@code keys}, creating or retrieving those values + * if necessary. The returned map contains entries that were already cached, combined with newly + * loaded entries; it will never contain null keys or values. + * + *

Caches loaded by a {@link CacheLoader} will issue a single request to + * {@link CacheLoader#loadAll} for all keys which are not already present in the cache. All + * entries returned by {@link CacheLoader#loadAll} will be stored in the cache, over-writing + * any previously cached values. This method will throw an exception if + * {@link CacheLoader#loadAll} returns {@code null}, returns a map containing null keys or values, + * or fails to return an entry for each requested key. + * + *

Note that duplicate elements in {@code keys}, as determined by {@link Object#equals}, will + * be ignored. + * + * @throws ExecutionException if a checked exception was thrown while loading the values + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * values + * @throws ExecutionError if an error was thrown while loading the values + * @since 11.0 + */ + ImmutableMap getAll(Iterable keys) throws ExecutionException; + + /** + * Discouraged. Provided to satisfy the {@code Function} interface; use {@link #get} or + * {@link #getUnchecked} instead. + * + * @throws UncheckedExecutionException if an exception was thrown while loading the value, + * regardless of whether the exception was checked or unchecked + * @throws ExecutionError if an error was thrown while loading the value + */ + @Override + V apply(K key); + + /** + * Loads a new value for key {@code key}, possibly asynchronously. While the new value is loading + * the previous value (if any) will continue to be returned by {@code get(key)} unless it is + * evicted. If the new value is loaded successfully it will replace the previous value in the + * cache; if an exception is thrown while refreshing the previous value will remain, and the + * exception will be logged (using {@link java.util.logging.Logger}) and swallowed. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#reload} if the + * cache currently contains a value for {@code key}, and {@link CacheLoader#load} otherwise. + * Loading is asynchronous only if {@link CacheLoader#reload} was overridden with an + * asynchronous implementation. + * + *

Returns without doing anything if another thread is currently loading the value for + * {@code key}. If the cache loader associated with this cache performs refresh asynchronously + * then this method may return before refresh completes. + * + * @since 11.0 + */ + void refresh(K key); + + /** + * {@inheritDoc} + * + *

Note that although the view is modifiable, no method on the returned map will ever + * cause entries to be automatically loaded. + */ + @Override + ConcurrentMap asMap(); +} diff --git a/guava/src/com/google/common/cache/LocalCache.java b/guava/src/com/google/common/cache/LocalCache.java new file mode 100644 index 0000000..6079b9b --- /dev/null +++ b/guava/src/com/google/common/cache/LocalCache.java @@ -0,0 +1,4913 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.cache.CacheBuilder.NULL_TICKER; +import static com.google.common.cache.CacheBuilder.UNSET_INT; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Equivalence; +import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; +import com.google.common.cache.AbstractCache.SimpleStatsCounter; +import com.google.common.cache.AbstractCache.StatsCounter; +import com.google.common.cache.CacheBuilder.NullListener; +import com.google.common.cache.CacheBuilder.OneWeigher; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; +import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException; +import com.google.common.collect.AbstractSequentialIterator; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.common.util.concurrent.Uninterruptibles; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractQueue; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * The concurrent hash map implementation built by {@link CacheBuilder}. + * + *

This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java. + * + * @author Charles Fry + * @author Bob Lee ({@code com.google.common.collect.MapMaker}) + * @author Doug Lea ({@code ConcurrentHashMap}) + */ +class LocalCache extends AbstractMap implements ConcurrentMap { + + /* + * The basic strategy is to subdivide the table among Segments, each of which itself is a + * concurrently readable hash table. The map supports non-blocking reads and concurrent writes + * across different segments. + * + * If a maximum size is specified, a best-effort bounding is performed per segment, using a + * page-replacement algorithm to determine which entries to evict when the capacity has been + * exceeded. + * + * The page replacement algorithm's data structures are kept casually consistent with the map. The + * ordering of writes to a segment is sequentially consistent. An update to the map and recording + * of reads may not be immediately reflected on the algorithm's data structures. These structures + * are guarded by a lock and operations are applied in batches to avoid lock contention. The + * penalty of applying the batches is spread across threads so that the amortized cost is slightly + * higher than performing just the operation without enforcing the capacity constraint. + * + * This implementation uses a per-segment queue to record a memento of the additions, removals, + * and accesses that were performed on the map. The queue is drained on writes and when it exceeds + * its capacity threshold. + * + * The Least Recently Used page replacement algorithm was chosen due to its simplicity, high hit + * rate, and ability to be implemented with O(1) time complexity. The initial LRU implementation + * operates per-segment rather than globally for increased implementation simplicity. We expect + * the cache hit rate to be similar to that of a global LRU algorithm. + */ + + // Constants + + /** + * 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 to ensure that entries are + * indexable using ints. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** The maximum number of segments to allow; used to bound constructor arguments. */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** Number of (unsynchronized) retries in the containsValue method. */ + static final int CONTAINS_VALUE_RETRIES = 3; + + /** + * Number of cache access operations that can be buffered per segment before the cache's recency + * ordering information is updated. This is used to avoid lock contention by recording a memento + * of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs. + * + *

This must be a (2^n)-1 as it is used as a mask. + */ + static final int DRAIN_THRESHOLD = 0x3F; + + /** + * Maximum number of entries to be drained in a single cleanup run. This applies independently to + * the cleanup queue and both reference queues. + */ + // TODO(fry): empirically optimize this + static final int DRAIN_MAX = 16; + + // Fields + + static final Logger logger = Logger.getLogger(LocalCache.class.getName()); + + static final ListeningExecutorService sameThreadExecutor = MoreExecutors.sameThreadExecutor(); + + /** + * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose + * the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. Helps prevent entries that end up in the same segment + * from also ending up in the same bucket. + */ + final int segmentShift; + + /** The segments, each of which is a specialized hash table. */ + final Segment[] segments; + + /** The concurrency level. */ + final int concurrencyLevel; + + /** Strategy for comparing keys. */ + final Equivalence keyEquivalence; + + /** Strategy for comparing values. */ + final Equivalence valueEquivalence; + + /** Strategy for referencing keys. */ + final Strength keyStrength; + + /** Strategy for referencing values. */ + final Strength valueStrength; + + /** The maximum weight of this map. UNSET_INT if there is no maximum. */ + final long maxWeight; + + /** Weigher to weigh cache entries. */ + final Weigher weigher; + + /** How long after the last access to an entry the map will retain that entry. */ + final long expireAfterAccessNanos; + + /** How long after the last write to an entry the map will retain that entry. */ + final long expireAfterWriteNanos; + + /** How long after the last write an entry becomes a candidate for refresh. */ + final long refreshNanos; + + /** Entries waiting to be consumed by the removal listener. */ + // TODO(fry): define a new type which creates event objects and automates the clear logic + final Queue> removalNotificationQueue; + + /** + * A listener that is invoked when an entry is removed due to expiration or garbage collection of + * soft/weak entries. + */ + final RemovalListener removalListener; + + /** Measures time in a testable way. */ + final Ticker ticker; + + /** Factory used to create new entries. */ + final EntryFactory entryFactory; + + /** + * Accumulates global cache statistics. Note that there are also per-segments stats counters + * which must be aggregated to obtain a global stats view. + */ + final StatsCounter globalStatsCounter; + + /** + * The default cache loader to use on loading operations. + */ + @Nullable + final CacheLoader defaultLoader; + + /** + * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. + */ + LocalCache( + CacheBuilder builder, @Nullable CacheLoader loader) { + concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + + keyStrength = builder.getKeyStrength(); + valueStrength = builder.getValueStrength(); + + keyEquivalence = builder.getKeyEquivalence(); + valueEquivalence = builder.getValueEquivalence(); + + maxWeight = builder.getMaximumWeight(); + weigher = builder.getWeigher(); + expireAfterAccessNanos = builder.getExpireAfterAccessNanos(); + expireAfterWriteNanos = builder.getExpireAfterWriteNanos(); + refreshNanos = builder.getRefreshNanos(); + + removalListener = builder.getRemovalListener(); + removalNotificationQueue = (removalListener == NullListener.INSTANCE) + ? LocalCache.>discardingQueue() + : new ConcurrentLinkedQueue>(); + + ticker = builder.getTicker(recordsTime()); + entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries()); + globalStatsCounter = builder.getStatsCounterSupplier().get(); + defaultLoader = loader; + + int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + if (evictsBySize() && !customWeigher()) { + initialCapacity = Math.min(initialCapacity, (int) maxWeight); + } + + // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless + // maximumSize/Weight is specified in which case ensure that each segment gets at least 10 + // entries. The special casing for size-based eviction is only necessary because that eviction + // happens per segment instead of globally, so too many segments compared to the maximum size + // will result in random eviction behavior. + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel + && (!evictsBySize() || segmentCount * 20 <= maxWeight)) { + ++segmentShift; + segmentCount <<= 1; + } + this.segmentShift = 32 - segmentShift; + segmentMask = segmentCount - 1; + + this.segments = newSegmentArray(segmentCount); + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + + if (evictsBySize()) { + // Ensure sum of segment max weights = overall max weights + long maxSegmentWeight = maxWeight / segmentCount + 1; + long remainder = maxWeight % segmentCount; + for (int i = 0; i < this.segments.length; ++i) { + if (i == remainder) { + maxSegmentWeight--; + } + this.segments[i] = + createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get()); + } + } else { + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = + createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get()); + } + } + } + + boolean evictsBySize() { + return maxWeight >= 0; + } + + boolean customWeigher() { + return weigher != OneWeigher.INSTANCE; + } + + boolean expires() { + return expiresAfterWrite() || expiresAfterAccess(); + } + + boolean expiresAfterWrite() { + return expireAfterWriteNanos > 0; + } + + boolean expiresAfterAccess() { + return expireAfterAccessNanos > 0; + } + + boolean refreshes() { + return refreshNanos > 0; + } + + boolean usesAccessQueue() { + return expiresAfterAccess() || evictsBySize(); + } + + boolean usesWriteQueue() { + return expiresAfterWrite(); + } + + boolean recordsWrite() { + return expiresAfterWrite() || refreshes(); + } + + boolean recordsAccess() { + return expiresAfterAccess(); + } + + boolean recordsTime() { + return recordsWrite() || recordsAccess(); + } + + boolean usesWriteEntries() { + return usesWriteQueue() || recordsWrite(); + } + + boolean usesAccessEntries() { + return usesAccessQueue() || recordsAccess(); + } + + boolean usesKeyReferences() { + return keyStrength != Strength.STRONG; + } + + boolean usesValueReferences() { + return valueStrength != Strength.STRONG; + } + + enum Strength { + /* + * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the + * value. This could save ~8 bytes per entry. + */ + + STRONG { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new StrongValueReference(value) + : new WeightedStrongValueReference(value, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.equals(); + } + }, + + SOFT { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new SoftValueReference(segment.valueReferenceQueue, value, entry) + : new WeightedSoftValueReference( + segment.valueReferenceQueue, value, entry, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }, + + WEAK { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new WeakValueReference(segment.valueReferenceQueue, value, entry) + : new WeightedWeakValueReference( + segment.valueReferenceQueue, value, entry, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }; + + /** + * Creates a reference for the given value according to this value strength. + */ + abstract ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight); + + /** + * Returns the default equivalence strategy used to compare and hash keys or values referenced + * at this strength. This strategy will be used unless the user explicitly specifies an + * alternate strategy. + */ + abstract Equivalence defaultEquivalence(); + } + + /** + * Creates new entries. + */ + enum EntryFactory { + STRONG { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongEntry(key, hash, next); + } + }, + STRONG_ACCESS { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongAccessEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + return newEntry; + } + }, + STRONG_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongWriteEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + STRONG_ACCESS_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongAccessWriteEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + + WEAK { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakEntry(segment.keyReferenceQueue, key, hash, next); + } + }, + WEAK_ACCESS { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakAccessEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + return newEntry; + } + }, + WEAK_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakWriteEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + WEAK_ACCESS_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakAccessWriteEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + copyWriteEntry(original, newEntry); + return newEntry; + } + }; + + /** + * Masks used to compute indices in the following table. + */ + static final int ACCESS_MASK = 1; + static final int WRITE_MASK = 2; + static final int WEAK_MASK = 4; + + /** + * Look-up table for factories. + */ + static final EntryFactory[] factories = { + STRONG, STRONG_ACCESS, STRONG_WRITE, STRONG_ACCESS_WRITE, + WEAK, WEAK_ACCESS, WEAK_WRITE, WEAK_ACCESS_WRITE, + }; + + static EntryFactory getFactory(Strength keyStrength, boolean usesAccessQueue, + boolean usesWriteQueue) { + int flags = ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0) + | (usesAccessQueue ? ACCESS_MASK : 0) + | (usesWriteQueue ? WRITE_MASK : 0); + return factories[flags]; + } + + /** + * Creates a new entry. + * + * @param segment to create the entry for + * @param key of the entry + * @param hash of the key + * @param next entry in the same bucket + */ + abstract ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next); + + /** + * Copies an entry, assigning it a new {@code next} entry. + * + * @param original the entry to copy + * @param newNext entry in the same bucket + */ + @GuardedBy("Segment.this") + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + return newEntry(segment, original.getKey(), original.getHash(), newNext); + } + + @GuardedBy("Segment.this") + void copyAccessEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectAccessOrder, nullifyAccessOrder. + newEntry.setAccessTime(original.getAccessTime()); + + connectAccessOrder(original.getPreviousInAccessQueue(), newEntry); + connectAccessOrder(newEntry, original.getNextInAccessQueue()); + + nullifyAccessOrder(original); + } + + @GuardedBy("Segment.this") + void copyWriteEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectWriteOrder, nullifyWriteOrder. + newEntry.setWriteTime(original.getWriteTime()); + + connectWriteOrder(original.getPreviousInWriteQueue(), newEntry); + connectWriteOrder(newEntry, original.getNextInWriteQueue()); + + nullifyWriteOrder(original); + } + } + + /** + * A reference to a value. + */ + interface ValueReference { + /** + * Returns the value. Does not block or throw exceptions. + */ + @Nullable + V get(); + + /** + * Waits for a value that may still be loading. Unlike get(), this method can block (in the + * case of FutureValueReference). + * + * @throws ExecutionException if the loading thread throws an exception + * @throws ExecutionError if the loading thread throws an error + */ + V waitForValue() throws ExecutionException; + + /** + * Returns the weight of this entry. This is assumed to be static between calls to setValue. + */ + int getWeight(); + + /** + * Returns the entry associated with this value reference, or {@code null} if this value + * reference is independent of any entry. + */ + @Nullable + ReferenceEntry getEntry(); + + /** + * Creates a copy of this reference for the given entry. + * + *

{@code value} may be null only for a loading reference. + */ + ValueReference copyFor( + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry); + + /** + * Notifify pending loads that a new value was set. This is only relevant to loading + * value references. + */ + void notifyNewValue(@Nullable V newValue); + + /** + * Returns true if a new value is currently loading, regardless of whether or not there is an + * existing value. It is assumed that the return value of this method is constant for any given + * ValueReference instance. + */ + boolean isLoading(); + + /** + * Returns true if this reference contains an active value, meaning one that is still considered + * present in the cache. Active values consist of live values, which are returned by cache + * lookups, and dead values, which have been evicted but awaiting removal. Non-active values + * consist strictly of loading values, though during refresh a value may be both active and + * loading. + */ + boolean isActive(); + } + + /** + * Placeholder. Indicates that the value hasn't been set yet. + */ + static final ValueReference UNSET = new ValueReference() { + @Override + public Object get() { + return null; + } + + @Override + public int getWeight() { + return 0; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor(ReferenceQueue queue, + @Nullable Object value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public Object waitForValue() { + return null; + } + + @Override + public void notifyNewValue(Object newValue) {} + }; + + /** + * Singleton placeholder that indicates a value is being loaded. + */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ValueReference unset() { + return (ValueReference) UNSET; + } + + /** + * An entry in a reference map. + * + * Entries in the map can be in the following states: + * + * Valid: + * - Live: valid key/value are set + * - Loading: loading is pending + * + * Invalid: + * - Expired: time expired (key/value may still be set) + * - Collected: key/value was partially collected, but not yet cleaned up + * - Unset: marked as unset, awaiting cleanup or reuse + */ + interface ReferenceEntry { + /** + * Returns the value reference from this entry. + */ + ValueReference getValueReference(); + + /** + * Sets the value reference for this entry. + */ + void setValueReference(ValueReference valueReference); + + /** + * Returns the next entry in the chain. + */ + @Nullable + ReferenceEntry getNext(); + + /** + * Returns the entry's hash. + */ + int getHash(); + + /** + * Returns the key for this entry. + */ + @Nullable + K getKey(); + + /* + * Used by entries that use access order. Access entries are maintained in a doubly-linked list. + * New entries are added at the tail of the list at write time; stale entries are expired from + * the head of the list. + */ + + /** + * Returns the time that this entry was last accessed, in ns. + */ + long getAccessTime(); + + /** + * Sets the entry access time in ns. + */ + void setAccessTime(long time); + + /** + * Returns the next entry in the access queue. + */ + ReferenceEntry getNextInAccessQueue(); + + /** + * Sets the next entry in the access queue. + */ + void setNextInAccessQueue(ReferenceEntry next); + + /** + * Returns the previous entry in the access queue. + */ + ReferenceEntry getPreviousInAccessQueue(); + + /** + * Sets the previous entry in the access queue. + */ + void setPreviousInAccessQueue(ReferenceEntry previous); + + /* + * Implemented by entries that use write order. Write entries are maintained in a + * doubly-linked list. New entries are added at the tail of the list at write time and stale + * entries are expired from the head of the list. + */ + + /** + * Returns the time that this entry was last written, in ns. + */ + long getWriteTime(); + + /** + * Sets the entry write time in ns. + */ + void setWriteTime(long time); + + /** + * Returns the next entry in the write queue. + */ + ReferenceEntry getNextInWriteQueue(); + + /** + * Sets the next entry in the write queue. + */ + void setNextInWriteQueue(ReferenceEntry next); + + /** + * Returns the previous entry in the write queue. + */ + ReferenceEntry getPreviousInWriteQueue(); + + /** + * Sets the previous entry in the write queue. + */ + void setPreviousInWriteQueue(ReferenceEntry previous); + } + + private enum NullEntry implements ReferenceEntry { + INSTANCE; + + @Override + public ValueReference getValueReference() { + return null; + } + + @Override + public void setValueReference(ValueReference valueReference) {} + + @Override + public ReferenceEntry getNext() { + return null; + } + + @Override + public int getHash() { + return 0; + } + + @Override + public Object getKey() { + return null; + } + + @Override + public long getAccessTime() { + return 0; + } + + @Override + public void setAccessTime(long time) {} + + @Override + public ReferenceEntry getNextInAccessQueue() { + return this; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return this; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) {} + + @Override + public long getWriteTime() { + return 0; + } + + @Override + public void setWriteTime(long time) {} + + @Override + public ReferenceEntry getNextInWriteQueue() { + return this; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return this; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) {} + } + + static abstract class AbstractReferenceEntry implements ReferenceEntry { + @Override + public ValueReference getValueReference() { + throw new UnsupportedOperationException(); + } + + @Override + public void setValueReference(ValueReference valueReference) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNext() { + throw new UnsupportedOperationException(); + } + + @Override + public int getHash() { + throw new UnsupportedOperationException(); + } + + @Override + public K getKey() { + throw new UnsupportedOperationException(); + } + + @Override + public long getAccessTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAccessTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + @Override + public long getWriteTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWriteTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + } + + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ReferenceEntry nullEntry() { + return (ReferenceEntry) NullEntry.INSTANCE; + } + + static final Queue DISCARDING_QUEUE = new AbstractQueue() { + @Override + public boolean offer(Object o) { + return true; + } + + @Override + public Object peek() { + return null; + } + + @Override + public Object poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return Iterators.emptyIterator(); + } + }; + + /** + * Queue that discards all elements. + */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static Queue discardingQueue() { + return (Queue) DISCARDING_QUEUE; + } + + /* + * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins! + * To maintain this code, make a change for the strong reference type. Then, cut and paste, and + * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that + * strong entries store the key reference directly while soft and weak entries delegate to their + * respective superclasses. + */ + + /** + * Used for strongly-referenced keys. + */ + static class StrongEntry implements ReferenceEntry { + final K key; + + StrongEntry(K key, int hash, @Nullable ReferenceEntry next) { + this.key = key; + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return this.key; + } + + // null access + + @Override + public long getAccessTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAccessTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null write + + @Override + public long getWriteTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWriteTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + this.valueReference = valueReference; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class StrongAccessEntry extends StrongEntry + implements ReferenceEntry { + StrongAccessEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + } + + static final class StrongWriteEntry + extends StrongEntry implements ReferenceEntry { + StrongWriteEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + static final class StrongAccessWriteEntry + extends StrongEntry implements ReferenceEntry { + StrongAccessWriteEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + /** + * Used for weakly-referenced keys. + */ + static class WeakEntry extends WeakReference implements ReferenceEntry { + WeakEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return get(); + } + + // null access + + @Override + public long getAccessTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAccessTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null write + + @Override + public long getWriteTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWriteTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + this.valueReference = valueReference; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class WeakAccessEntry + extends WeakEntry implements ReferenceEntry { + WeakAccessEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + } + + static final class WeakWriteEntry + extends WeakEntry implements ReferenceEntry { + WeakWriteEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + static final class WeakAccessWriteEntry + extends WeakEntry implements ReferenceEntry { + WeakAccessWriteEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + /** + * References a weak value. + */ + static class WeakValueReference + extends WeakReference implements ValueReference { + final ReferenceEntry entry; + + WeakValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void notifyNewValue(V newValue) {} + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeakValueReference(queue, value, entry); + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** + * References a soft value. + */ + static class SoftValueReference + extends SoftReference implements ValueReference { + final ReferenceEntry entry; + + SoftValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void notifyNewValue(V newValue) {} + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new SoftValueReference(queue, value, entry); + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** + * References a strong value. + */ + static class StrongValueReference implements ValueReference { + final V referent; + + StrongValueReference(V referent) { + this.referent = referent; + } + + @Override + public V get() { + return referent; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + + @Override + public void notifyNewValue(V newValue) {} + } + + /** + * References a weak value. + */ + static final class WeightedWeakValueReference extends WeakValueReference { + final int weight; + + WeightedWeakValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry, + int weight) { + super(queue, referent, entry); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeightedWeakValueReference(queue, value, entry, weight); + } + } + + /** + * References a soft value. + */ + static final class WeightedSoftValueReference extends SoftValueReference { + final int weight; + + WeightedSoftValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry, + int weight) { + super(queue, referent, entry); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeightedSoftValueReference(queue, value, entry, weight); + } + + } + + /** + * References a strong value. + */ + static final class WeightedStrongValueReference extends StrongValueReference { + final int weight; + + WeightedStrongValueReference(V referent, int weight) { + super(referent); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + } + + /** + * Applies a supplemental hash function to a given hash code, which defends against poor quality + * hash functions. This is critical when the concurrent hash map uses power-of-two length hash + * tables, that otherwise encounter collisions for hash codes that do not differ in lower or + * upper bits. + * + * @param h hash code + */ + static int rehash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + // TODO(kevinb): use Hashing/move this to Hashing? + h += (h << 15) ^ 0xffffcd7d; + h ^= (h >>> 10); + h += (h << 3); + h ^= (h >>> 6); + h += (h << 2) + (h << 14); + return h ^ (h >>> 16); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { + return segmentFor(hash).newEntry(key, hash, next); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + int hash = original.getHash(); + return segmentFor(hash).copyEntry(original, newNext); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#setValue} instead. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ValueReference newValueReference(ReferenceEntry entry, V value, int weight) { + int hash = entry.getHash(); + return valueStrength.referenceValue(segmentFor(hash), entry, value, weight); + } + + int hash(Object key) { + int h = keyEquivalence.hash(key); + return rehash(h); + } + + void reclaimValue(ValueReference valueReference) { + ReferenceEntry entry = valueReference.getEntry(); + int hash = entry.getHash(); + segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference); + } + + void reclaimKey(ReferenceEntry entry) { + int hash = entry.getHash(); + segmentFor(hash).reclaimKey(entry, hash); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#getLiveValue} + * instead. + */ + @VisibleForTesting + boolean isLive(ReferenceEntry entry, long now) { + return segmentFor(entry.getHash()).getLiveValue(entry, now) != null; + } + + /** + * Returns the segment that should be used for a key with the given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + // TODO(fry): Lazily create segments? + return segments[(hash >>> segmentShift) & segmentMask]; + } + + Segment createSegment( + int initialCapacity, long maxSegmentWeight, StatsCounter statsCounter) { + return new Segment(this, initialCapacity, maxSegmentWeight, statsCounter); + } + + /** + * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, + * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to + * cleanup stale entries. As such it should only be called outside of a segment context, such as + * during iteration. + */ + @Nullable + V getLiveValue(ReferenceEntry entry, long now) { + if (entry.getKey() == null) { + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + return null; + } + + if (isExpired(entry, now)) { + return null; + } + return value; + } + + // expiration + + /** + * Returns true if the entry has expired. + */ + boolean isExpired(ReferenceEntry entry, long now) { + if (expiresAfterAccess() + && (now - entry.getAccessTime() > expireAfterAccessNanos)) { + return true; + } + if (expiresAfterWrite() + && (now - entry.getWriteTime() > expireAfterWriteNanos)) { + return true; + } + return false; + } + + // queues + + @GuardedBy("Segment.this") + static void connectAccessOrder(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextInAccessQueue(next); + next.setPreviousInAccessQueue(previous); + } + + @GuardedBy("Segment.this") + static void nullifyAccessOrder(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextInAccessQueue(nullEntry); + nulled.setPreviousInAccessQueue(nullEntry); + } + + @GuardedBy("Segment.this") + static void connectWriteOrder(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextInWriteQueue(next); + next.setPreviousInWriteQueue(previous); + } + + @GuardedBy("Segment.this") + static void nullifyWriteOrder(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextInWriteQueue(nullEntry); + nulled.setPreviousInWriteQueue(nullEntry); + } + + /** + * Notifies listeners that an entry has been automatically removed due to expiration, eviction, + * or eligibility for garbage collection. This should be called every time expireEntries or + * evictEntry is called (once the lock is released). + */ + void processPendingNotifications() { + RemovalNotification notification; + while ((notification = removalNotificationQueue.poll()) != null) { + try { + removalListener.onRemoval(notification); + } catch (Throwable e) { + logger.log(Level.WARNING, "Exception thrown by removal listener", e); + } + } + } + + @SuppressWarnings("unchecked") + final Segment[] newSegmentArray(int ssize) { + return new Segment[ssize]; + } + + // Inner Classes + + /** + * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock + * opportunistically, just to simplify some locking and avoid separate construction. + */ + @SuppressWarnings("serial") // This class is never serialized. + static class Segment extends ReentrantLock { + + /* + * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class. + * It will require more memory but will reduce indirection. + */ + + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can + * be read without locking. Next fields of nodes are immutable (final). All list additions are + * performed at the front of each bin. This makes it easy to check changes, and also fast to + * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This + * works well for hash tables since the bin lists tend to be short. (The average length is less + * than two.) + * + * Read operations can thus proceed without locking, but rely on selected uses of volatiles to + * ensure that completed write operations performed by other threads are noticed. For most + * purposes, the "count" field, tracking the number of elements, serves as that volatile + * variable ensuring visibility. This is convenient because this field needs to be read in many + * read operations anyway: + * + * - All (unsynchronized) read operations must first read the "count" field, and should not + * look at table entries if it is 0. + * + * - All (synchronized) write operations should write to the "count" field after structurally + * changing any bin. The operations must not take any action that could even momentarily + * cause a concurrent read operation to see inconsistent data. This is made easier by the + * nature of the read operations in Map. For example, no operation can reveal that the table + * has grown but the threshold has not yet been updated, so there are no atomicity requirements + * for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field are marked in code + * comments. + */ + + final LocalCache map; + + /** + * The number of live elements in this segment's region. + */ + volatile int count; + + /** + * The weight of the live elements in this segment's region. + */ + @GuardedBy("Segment.this") + int totalWeight; + + /** + * Number of updates that alter the size of the table. This is used during bulk-read methods to + * make sure they see a consistent snapshot: If modCounts change during a traversal of segments + * loading size or checking containsValue, then we might have an inconsistent view of state + * so (usually) must retry. + */ + int modCount; + + /** + * The table is expanded when its size exceeds this threshold. (The value of this field is + * always {@code (int)(capacity * 0.75)}.) + */ + int threshold; + + /** + * The per-segment table. + */ + volatile AtomicReferenceArray> table; + + /** + * The maximum weight of this segment. UNSET_INT if there is no maximum. + */ + final long maxSegmentWeight; + + /** + * The key reference queue contains entries whose keys have been garbage collected, and which + * need to be cleaned up internally. + */ + final ReferenceQueue keyReferenceQueue; + + /** + * The value reference queue contains value references whose values have been garbage collected, + * and which need to be cleaned up internally. + */ + final ReferenceQueue valueReferenceQueue; + + /** + * The recency queue is used to record which entries were accessed for updating the access + * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is + * crossed or a write occurs on the segment. + */ + final Queue> recencyQueue; + + /** + * A counter of the number of reads since the last write, used to drain queues on a small + * fraction of read operations. + */ + final AtomicInteger readCount = new AtomicInteger(); + + /** + * A queue of elements currently in the map, ordered by write time. Elements are added to the + * tail of the queue on write. + */ + @GuardedBy("Segment.this") + final Queue> writeQueue; + + /** + * A queue of elements currently in the map, ordered by access time. Elements are added to the + * tail of the queue on access (note that writes count as accesses). + */ + @GuardedBy("Segment.this") + final Queue> accessQueue; + + /** Accumulates cache statistics. */ + final StatsCounter statsCounter; + + Segment(LocalCache map, int initialCapacity, long maxSegmentWeight, + StatsCounter statsCounter) { + this.map = map; + this.maxSegmentWeight = maxSegmentWeight; + this.statsCounter = statsCounter; + initTable(newEntryArray(initialCapacity)); + + keyReferenceQueue = map.usesKeyReferences() + ? new ReferenceQueue() : null; + + valueReferenceQueue = map.usesValueReferences() + ? new ReferenceQueue() : null; + + recencyQueue = map.usesAccessQueue() + ? new ConcurrentLinkedQueue>() + : LocalCache.>discardingQueue(); + + writeQueue = map.usesWriteQueue() + ? new WriteQueue() + : LocalCache.>discardingQueue(); + + accessQueue = map.usesAccessQueue() + ? new AccessQueue() + : LocalCache.>discardingQueue(); + } + + AtomicReferenceArray> newEntryArray(int size) { + return new AtomicReferenceArray>(size); + } + + void initTable(AtomicReferenceArray> newTable) { + this.threshold = newTable.length() * 3 / 4; // 0.75 + if (!map.customWeigher() && this.threshold == maxSegmentWeight) { + // prevent spurious expansion before eviction + this.threshold++; + } + this.table = newTable; + } + + @GuardedBy("Segment.this") + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { + return map.entryFactory.newEntry(this, key, hash, next); + } + + /** + * Copies {@code original} into a new entry chained to {@code newNext}. Returns the new entry, + * or {@code null} if {@code original} was already garbage collected. + */ + @GuardedBy("Segment.this") + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + if (original.getKey() == null) { + // key collected + return null; + } + + ValueReference valueReference = original.getValueReference(); + V value = valueReference.get(); + if ((value == null) && valueReference.isActive()) { + // value collected + return null; + } + + ReferenceEntry newEntry = map.entryFactory.copyEntry(this, original, newNext); + newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry)); + return newEntry; + } + + /** + * Sets a new value of an entry. Adds newly created entries at the end of the access queue. + */ + @GuardedBy("Segment.this") + void setValue(ReferenceEntry entry, K key, V value, long now) { + ValueReference previous = entry.getValueReference(); + int weight = map.weigher.weigh(key, value); + checkState(weight >= 0, "Weights must be non-negative"); + + ValueReference valueReference = + map.valueStrength.referenceValue(this, entry, value, weight); + entry.setValueReference(valueReference); + recordWrite(entry, weight, now); + previous.notifyNewValue(value); + } + + // loading + + V get(K key, int hash, CacheLoader loader) throws ExecutionException { + try { + if (count != 0) { // read-volatile + // don't call getLiveEntry, which would ignore loading values + ReferenceEntry e = getEntry(key, hash); + if (e != null) { + long now = map.ticker.read(); + V value = getLiveValue(e, now); + if (value != null) { + recordRead(e, now); + statsCounter.recordHits(1); + return scheduleRefresh(e, key, hash, value, now, loader); + } + ValueReference valueReference = e.getValueReference(); + if (valueReference.isLoading()) { + return waitForLoadingValue(e, key, valueReference); + } + } + } + + // at this point e is either null or expired; + return lockedGetOrLoad(key, hash, loader); + } catch (ExecutionException ee) { + Throwable cause = ee.getCause(); + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } else if (cause instanceof RuntimeException) { + throw new UncheckedExecutionException(cause); + } + throw ee; + } finally { + postReadCleanup(); + } + } + + V lockedGetOrLoad(K key, int hash, CacheLoader loader) + throws ExecutionException { + ReferenceEntry e; + ValueReference valueReference = null; + LoadingValueReference loadingValueReference = null; + boolean createNewEntry = true; + + lock(); + try { + // re-read ticker once inside the lock + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + valueReference = e.getValueReference(); + if (valueReference.isLoading()) { + createNewEntry = false; + } else { + V value = valueReference.get(); + if (value == null) { + enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED); + } else if (map.isExpired(e, now)) { + // This is a duplicate check, as preWriteCleanup already purged expired + // entries, but let's accomodate an incorrect expiration queue. + enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED); + } else { + recordLockedRead(e, now); + statsCounter.recordHits(1); + // we were concurrent with loading; don't consider refresh + return value; + } + + // immediately reuse invalid entries + writeQueue.remove(e); + accessQueue.remove(e); + this.count = newCount; // write-volatile + } + break; + } + } + + if (createNewEntry) { + loadingValueReference = new LoadingValueReference(); + + if (e == null) { + e = newEntry(key, hash, first); + e.setValueReference(loadingValueReference); + table.set(index, e); + } else { + e.setValueReference(loadingValueReference); + } + } + } finally { + unlock(); + postWriteCleanup(); + } + + if (createNewEntry) { + try { + // Synchronizes on the entry to allow failing fast when a recursive load is + // detected. This may be circumvented when an entry is copied, but will fail fast most + // of the time. + synchronized (e) { + return loadSync(key, hash, loadingValueReference, loader); + } + } finally { + statsCounter.recordMisses(1); + } + } else { + // The entry already exists. Wait for loading. + return waitForLoadingValue(e, key, valueReference); + } + } + + V waitForLoadingValue(ReferenceEntry e, K key, ValueReference valueReference) + throws ExecutionException { + if (!valueReference.isLoading()) { + throw new AssertionError(); + } + + checkState(!Thread.holdsLock(e), "Recursive load"); + // don't consider expiration as we're concurrent with loading + try { + V value = valueReference.waitForValue(); + if (value == null) { + throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); + } + // re-read ticker now that loading has completed + long now = map.ticker.read(); + recordRead(e, now); + return value; + } finally { + statsCounter.recordMisses(1); + } + } + + // at most one of loadSync/loadAsync may be called for any given LoadingValueReference + + V loadSync(K key, int hash, LoadingValueReference loadingValueReference, + CacheLoader loader) throws ExecutionException { + ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); + return getAndRecordStats(key, hash, loadingValueReference, loadingFuture); + } + + ListenableFuture loadAsync(final K key, final int hash, + final LoadingValueReference loadingValueReference, CacheLoader loader) { + final ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); + loadingFuture.addListener( + new Runnable() { + @Override + public void run() { + try { + V newValue = getAndRecordStats(key, hash, loadingValueReference, loadingFuture); + // update loadingFuture for the sake of other pending requests + loadingValueReference.set(newValue); + } catch (Throwable t) { + logger.log(Level.WARNING, "Exception thrown during refresh", t); + loadingValueReference.setException(t); + } + } + }, sameThreadExecutor); + return loadingFuture; + } + + /** + * Waits uninterruptibly for {@code newValue} to be loaded, and then records loading stats. + */ + V getAndRecordStats(K key, int hash, LoadingValueReference loadingValueReference, + ListenableFuture newValue) throws ExecutionException { + V value = null; + try { + value = getUninterruptibly(newValue); + if (value == null) { + throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); + } + statsCounter.recordLoadSuccess(loadingValueReference.elapsedNanos()); + storeLoadedValue(key, hash, loadingValueReference, value); + return value; + } finally { + if (value == null) { + statsCounter.recordLoadException(loadingValueReference.elapsedNanos()); + removeLoadingValue(key, hash, loadingValueReference); + } + } + } + + V scheduleRefresh(ReferenceEntry entry, K key, int hash, V oldValue, long now, + CacheLoader loader) { + if (map.refreshes() && (now - entry.getWriteTime() > map.refreshNanos)) { + V newValue = refresh(key, hash, loader); + if (newValue != null) { + return newValue; + } + } + return oldValue; + } + + /** + * Refreshes the value associated with {@code key}, unless another thread is already doing so. + * Returns the newly refreshed value associated with {@code key} if it was refreshed inline, or + * {@code null} if another thread is performing the refresh or if an error occurs during + * refresh. + */ + @Nullable + V refresh(K key, int hash, CacheLoader loader) { + final LoadingValueReference loadingValueReference = + insertLoadingValueReference(key, hash); + if (loadingValueReference == null) { + return null; + } + + ListenableFuture result = loadAsync(key, hash, loadingValueReference, loader); + if (result.isDone()) { + try { + return Uninterruptibles.getUninterruptibly(result); + } catch (Throwable t) { + // don't let refresh exceptions propagate; error was already logged + } + } + return null; + } + + /** + * Returns a newly inserted {@code LoadingValueReference}, or null if the live value reference + * is already loading. + */ + @Nullable + LoadingValueReference insertLoadingValueReference(final K key, final int hash) { + ReferenceEntry e = null; + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + // Look for an existing entry. + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + ValueReference valueReference = e.getValueReference(); + if (valueReference.isLoading()) { + // refresh is a no-op if loading is pending + return null; + } + + // continue returning old value while loading + ++modCount; + LoadingValueReference loadingValueReference = + new LoadingValueReference(valueReference); + e.setValueReference(loadingValueReference); + return loadingValueReference; + } + } + + ++modCount; + LoadingValueReference loadingValueReference = new LoadingValueReference(); + e = newEntry(key, hash, first); + e.setValueReference(loadingValueReference); + table.set(index, e); + return loadingValueReference; + } finally { + unlock(); + postWriteCleanup(); + } + } + + // reference queues, for garbage collection cleanup + + /** + * Cleanup collected entries when the lock is available. + */ + void tryDrainReferenceQueues() { + if (tryLock()) { + try { + drainReferenceQueues(); + } finally { + unlock(); + } + } + } + + /** + * Drain the key and value reference queues, cleaning up internal entries containing garbage + * collected keys or values. + */ + @GuardedBy("Segment.this") + void drainReferenceQueues() { + if (map.usesKeyReferences()) { + drainKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + drainValueReferenceQueue(); + } + } + + @GuardedBy("Segment.this") + void drainKeyReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = keyReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ReferenceEntry entry = (ReferenceEntry) ref; + map.reclaimKey(entry); + if (++i == DRAIN_MAX) { + break; + } + } + } + + @GuardedBy("Segment.this") + void drainValueReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = valueReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ValueReference valueReference = (ValueReference) ref; + map.reclaimValue(valueReference); + if (++i == DRAIN_MAX) { + break; + } + } + } + + /** + * Clears all entries from the key and value reference queues. + */ + void clearReferenceQueues() { + if (map.usesKeyReferences()) { + clearKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + clearValueReferenceQueue(); + } + } + + void clearKeyReferenceQueue() { + while (keyReferenceQueue.poll() != null) {} + } + + void clearValueReferenceQueue() { + while (valueReferenceQueue.poll() != null) {} + } + + // recency queue, shared by expiration and eviction + + /** + * Records the relative order in which this read was performed by adding {@code entry} to the + * recency queue. At write-time, or when the queue is full past the threshold, the queue will + * be drained and the entries therein processed. + * + *

Note: locked reads should use {@link #recordLockedRead}. + */ + void recordRead(ReferenceEntry entry, long now) { + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + recencyQueue.add(entry); + } + + /** + * Updates the eviction metadata that {@code entry} was just read. This currently amounts to + * adding {@code entry} to relevant eviction lists. + * + *

Note: this method should only be called under lock, as it directly manipulates the + * eviction queues. Unlocked reads should use {@link #recordRead}. + */ + @GuardedBy("Segment.this") + void recordLockedRead(ReferenceEntry entry, long now) { + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + accessQueue.add(entry); + } + + /** + * Updates eviction metadata that {@code entry} was just written. This currently amounts to + * adding {@code entry} to relevant eviction lists. + */ + @GuardedBy("Segment.this") + void recordWrite(ReferenceEntry entry, int weight, long now) { + // we are already under lock, so drain the recency queue immediately + drainRecencyQueue(); + totalWeight += weight; + + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + if (map.recordsWrite()) { + entry.setWriteTime(now); + } + accessQueue.add(entry); + writeQueue.add(entry); + } + + /** + * Drains the recency queue, updating eviction metadata that the entries therein were read in + * the specified relative order. This currently amounts to adding them to relevant eviction + * lists (accounting for the fact that they could have been removed from the map since being + * added to the recency queue). + */ + @GuardedBy("Segment.this") + void drainRecencyQueue() { + ReferenceEntry e; + while ((e = recencyQueue.poll()) != null) { + // An entry may be in the recency queue despite it being removed from + // the map . This can occur when the entry was concurrently read while a + // writer is removing it from the segment or after a clear has removed + // all of the segment's entries. + if (accessQueue.contains(e)) { + accessQueue.add(e); + } + } + } + + // expiration + + /** + * Cleanup expired entries when the lock is available. + */ + void tryExpireEntries(long now) { + if (tryLock()) { + try { + expireEntries(now); + } finally { + unlock(); + // don't call postWriteCleanup as we're in a read + } + } + } + + @GuardedBy("Segment.this") + void expireEntries(long now) { + drainRecencyQueue(); + + ReferenceEntry e; + while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) { + if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { + throw new AssertionError(); + } + } + while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) { + if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { + throw new AssertionError(); + } + } + } + + // eviction + + @GuardedBy("Segment.this") + void enqueueNotification(ReferenceEntry entry, RemovalCause cause) { + enqueueNotification(entry.getKey(), entry.getHash(), entry.getValueReference(), cause); + } + + @GuardedBy("Segment.this") + void enqueueNotification(@Nullable K key, int hash, ValueReference valueReference, + RemovalCause cause) { + totalWeight -= valueReference.getWeight(); + if (cause.wasEvicted()) { + statsCounter.recordEviction(); + } + if (map.removalNotificationQueue != DISCARDING_QUEUE) { + V value = valueReference.get(); + RemovalNotification notification = new RemovalNotification(key, value, cause); + map.removalNotificationQueue.offer(notification); + } + } + + /** + * Performs eviction if the segment is full. This should only be called prior to adding a new + * entry and increasing {@code count}. + */ + @GuardedBy("Segment.this") + void evictEntries() { + if (!map.evictsBySize()) { + return; + } + + drainRecencyQueue(); + while (totalWeight > maxSegmentWeight) { + ReferenceEntry e = getNextEvictable(); + if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) { + throw new AssertionError(); + } + } + } + + // TODO(fry): instead implement this with an eviction head + ReferenceEntry getNextEvictable() { + for (ReferenceEntry e : accessQueue) { + int weight = e.getValueReference().getWeight(); + if (weight > 0) { + return e; + } + } + throw new AssertionError(); + } + + /** + * Returns first entry of bin for given hash. + */ + ReferenceEntry getFirst(int hash) { + // read this volatile field only once + AtomicReferenceArray> table = this.table; + return table.get(hash & (table.length() - 1)); + } + + // Specialized implementations of map methods + + @Nullable + ReferenceEntry getEntry(Object key, int hash) { + for (ReferenceEntry e = getFirst(hash); e != null; e = e.getNext()) { + if (e.getHash() != hash) { + continue; + } + + K entryKey = e.getKey(); + if (entryKey == null) { + tryDrainReferenceQueues(); + continue; + } + + if (map.keyEquivalence.equivalent(key, entryKey)) { + return e; + } + } + + return null; + } + + @Nullable + ReferenceEntry getLiveEntry(Object key, int hash, long now) { + ReferenceEntry e = getEntry(key, hash); + if (e == null) { + return null; + } else if (map.isExpired(e, now)) { + tryExpireEntries(now); + return null; + } + return e; + } + + /** + * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, + * loading, or expired. + */ + V getLiveValue(ReferenceEntry entry, long now) { + if (entry.getKey() == null) { + tryDrainReferenceQueues(); + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + tryDrainReferenceQueues(); + return null; + } + + if (map.isExpired(entry, now)) { + tryExpireEntries(now); + return null; + } + return value; + } + + @Nullable + V get(Object key, int hash) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + ReferenceEntry e = getLiveEntry(key, hash, now); + if (e == null) { + return null; + } + + V value = e.getValueReference().get(); + if (value != null) { + recordRead(e, now); + return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader); + } + tryDrainReferenceQueues(); + } + return null; + } finally { + postReadCleanup(); + } + } + + boolean containsKey(Object key, int hash) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + ReferenceEntry e = getLiveEntry(key, hash, now); + if (e == null) { + return false; + } + return e.getValueReference().get() != null; + } + + return false; + } finally { + postReadCleanup(); + } + } + + /** + * This method is a convenience for testing. Code should call {@link + * LocalCache#containsValue} directly. + */ + @VisibleForTesting + boolean containsValue(Object value) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + AtomicReferenceArray> table = this.table; + int length = table.length(); + for (int i = 0; i < length; ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + V entryValue = getLiveValue(e, now); + if (entryValue == null) { + continue; + } + if (map.valueEquivalence.equivalent(value, entryValue)) { + return true; + } + } + } + } + + return false; + } finally { + postReadCleanup(); + } + } + + @Nullable + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + // Look for an existing entry. + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + if (entryValue == null) { + ++modCount; + if (valueReference.isActive()) { + enqueueNotification(key, hash, valueReference, RemovalCause.COLLECTED); + setValue(e, key, value, now); + newCount = this.count; // count remains unchanged + } else { + setValue(e, key, value, now); + newCount = this.count + 1; + } + this.count = newCount; // write-volatile + evictEntries(); + return null; + } else if (onlyIfAbsent) { + // Mimic + // "if (!map.containsKey(key)) ... + // else return map.get(key); + recordLockedRead(e, now); + return entryValue; + } else { + // clobber existing entry, count remains unchanged + ++modCount; + enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED); + setValue(e, key, value, now); + evictEntries(); + return entryValue; + } + } + } + + // Create a new entry. + ++modCount; + ReferenceEntry newEntry = newEntry(key, hash, first); + setValue(newEntry, key, value, now); + table.set(index, newEntry); + newCount = this.count + 1; + this.count = newCount; // write-volatile + evictEntries(); + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** + * Expands the table if possible. + */ + @GuardedBy("Segment.this") + void expand() { + AtomicReferenceArray> oldTable = table; + int oldCapacity = oldTable.length(); + if (oldCapacity >= MAXIMUM_CAPACITY) { + return; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move with a power of two offset. + * We eliminate unnecessary node creation by catching cases where old nodes can be reused + * because their next fields won't change. Statistically, at the default threshold, only + * about one-sixth of them need cloning when a table doubles. The nodes they replace will be + * garbage collectable as soon as they are no longer referenced by any reader thread that may + * be in the midst of traversing table right now. + */ + + int newCount = count; + AtomicReferenceArray> newTable = newEntryArray(oldCapacity << 1); + threshold = newTable.length() * 3 / 4; + int newMask = newTable.length() - 1; + for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + ReferenceEntry head = oldTable.get(oldIndex); + + if (head != null) { + ReferenceEntry next = head.getNext(); + int headIndex = head.getHash() & newMask; + + // Single node on list + if (next == null) { + newTable.set(headIndex, head); + } else { + // Reuse the consecutive sequence of nodes with the same target + // index from the end of the list. tail points to the first + // entry in the reusable list. + ReferenceEntry tail = head; + int tailIndex = headIndex; + for (ReferenceEntry e = next; e != null; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + if (newIndex != tailIndex) { + // The index changed. We'll need to copy the previous entry. + tailIndex = newIndex; + tail = e; + } + } + newTable.set(tailIndex, tail); + + // Clone nodes leading up to the tail. + for (ReferenceEntry e = head; e != tail; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + ReferenceEntry newNext = newTable.get(newIndex); + ReferenceEntry newFirst = copyEntry(e, newNext); + if (newFirst != null) { + newTable.set(newIndex, newFirst); + } else { + removeCollectedEntry(e); + newCount--; + } + } + } + } + } + table = newTable; + this.count = newCount; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (valueReference.isActive()) { + // If the value disappeared, this entry is partially collected. + int newCount = this.count - 1; + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return false; + } + + if (map.valueEquivalence.equivalent(oldValue, entryValue)) { + ++modCount; + enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED); + setValue(e, key, newValue, now); + evictEntries(); + return true; + } else { + // Mimic + // "if (map.containsKey(key) && map.get(key).equals(oldValue))..." + recordLockedRead(e, now); + return false; + } + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + @Nullable + V replace(K key, int hash, V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (valueReference.isActive()) { + // If the value disappeared, this entry is partially collected. + int newCount = this.count - 1; + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return null; + } + + ++modCount; + enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED); + setValue(e, key, newValue, now); + evictEntries(); + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + @Nullable + V remove(Object key, int hash) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (entryValue != null) { + cause = RemovalCause.EXPLICIT; + } else if (valueReference.isActive()) { + cause = RemovalCause.COLLECTED; + } else { + // currently loading + return null; + } + + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, entryKey, hash, valueReference, cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + boolean storeLoadedValue(K key, int hash, LoadingValueReference oldValueReference, + V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + // replace the old LoadingValueReference if it's live, otherwise + // perform a putIfAbsent + if (oldValueReference == valueReference + || (entryValue == null && valueReference != UNSET)) { + ++modCount; + if (oldValueReference.isActive()) { + RemovalCause cause = + (entryValue == null) ? RemovalCause.COLLECTED : RemovalCause.REPLACED; + enqueueNotification(key, hash, oldValueReference, cause); + newCount--; + } + setValue(e, key, newValue, now); + this.count = newCount; // write-volatile + evictEntries(); + return true; + } + + // the loaded value was already clobbered + valueReference = new WeightedStrongValueReference(newValue, 0); + enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED); + return false; + } + } + + ++modCount; + ReferenceEntry newEntry = newEntry(key, hash, first); + setValue(newEntry, key, newValue, now); + table.set(index, newEntry); + this.count = newCount; // write-volatile + evictEntries(); + return true; + } finally { + unlock(); + postWriteCleanup(); + } + } + + boolean remove(Object key, int hash, Object value) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (map.valueEquivalence.equivalent(value, entryValue)) { + cause = RemovalCause.EXPLICIT; + } else if (entryValue == null && valueReference.isActive()) { + cause = RemovalCause.COLLECTED; + } else { + // currently loading + return false; + } + + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, entryKey, hash, valueReference, cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return (cause == RemovalCause.EXPLICIT); + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + void clear() { + if (count != 0) { // read-volatile + lock(); + try { + AtomicReferenceArray> table = this.table; + for (int i = 0; i < table.length(); ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + // Loading references aren't actually in the map yet. + if (e.getValueReference().isActive()) { + enqueueNotification(e, RemovalCause.EXPLICIT); + } + } + } + for (int i = 0; i < table.length(); ++i) { + table.set(i, null); + } + clearReferenceQueues(); + writeQueue.clear(); + accessQueue.clear(); + readCount.set(0); + + ++modCount; + count = 0; // write-volatile + } finally { + unlock(); + postWriteCleanup(); + } + } + } + + @GuardedBy("Segment.this") + @Nullable + ReferenceEntry removeValueFromChain(ReferenceEntry first, + ReferenceEntry entry, @Nullable K key, int hash, ValueReference valueReference, + RemovalCause cause) { + enqueueNotification(key, hash, valueReference, cause); + writeQueue.remove(entry); + accessQueue.remove(entry); + + if (valueReference.isLoading()) { + valueReference.notifyNewValue(null); + return first; + } else { + return removeEntryFromChain(first, entry); + } + } + + @GuardedBy("Segment.this") + @Nullable + ReferenceEntry removeEntryFromChain(ReferenceEntry first, + ReferenceEntry entry) { + int newCount = count; + ReferenceEntry newFirst = entry.getNext(); + for (ReferenceEntry e = first; e != entry; e = e.getNext()) { + ReferenceEntry next = copyEntry(e, newFirst); + if (next != null) { + newFirst = next; + } else { + removeCollectedEntry(e); + newCount--; + } + } + this.count = newCount; + return newFirst; + } + + @GuardedBy("Segment.this") + void removeCollectedEntry(ReferenceEntry entry) { + enqueueNotification(entry, RemovalCause.COLLECTED); + writeQueue.remove(entry); + accessQueue.remove(entry); + } + + /** + * Removes an entry whose key has been garbage collected. + */ + boolean reclaimKey(ReferenceEntry entry, int hash) { + lock(); + try { + int newCount = count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, e.getKey(), hash, e.getValueReference(), RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** + * Removes an entry whose value has been garbage collected. + */ + boolean reclaimValue(K key, int hash, ValueReference valueReference) { + lock(); + try { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + if (!isHeldByCurrentThread()) { // don't cleanup inside of put + postWriteCleanup(); + } + } + } + + boolean removeLoadingValue(K key, int hash, LoadingValueReference valueReference) { + lock(); + try { + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + if (valueReference.isActive()) { + e.setValueReference(valueReference.getOldValue()); + } else { + ReferenceEntry newFirst = removeEntryFromChain(first, e); + table.set(index, newFirst); + } + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + @GuardedBy("Segment.this") + boolean removeEntry(ReferenceEntry entry, int hash, RemovalCause cause) { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + ReferenceEntry newFirst = removeValueFromChain( + first, e, e.getKey(), hash, e.getValueReference(), cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } + + /** + * Performs routine cleanup following a read. Normally cleanup happens during writes. If cleanup + * is not observed after a sufficient number of reads, try cleaning up from the read thread. + */ + void postReadCleanup() { + if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) { + cleanUp(); + } + } + + /** + * Performs routine cleanup prior to executing a write. This should be called every time a + * write thread acquires the segment lock, immediately after acquiring the lock. + * + *

Post-condition: expireEntries has been run. + */ + @GuardedBy("Segment.this") + void preWriteCleanup(long now) { + runLockedCleanup(now); + } + + /** + * Performs routine cleanup following a write. + */ + void postWriteCleanup() { + runUnlockedCleanup(); + } + + void cleanUp() { + long now = map.ticker.read(); + runLockedCleanup(now); + runUnlockedCleanup(); + } + + void runLockedCleanup(long now) { + if (tryLock()) { + try { + drainReferenceQueues(); + expireEntries(now); // calls drainRecencyQueue + readCount.set(0); + } finally { + unlock(); + } + } + } + + void runUnlockedCleanup() { + // locked cleanup may generate notifications we can send unlocked + if (!isHeldByCurrentThread()) { + map.processPendingNotifications(); + } + } + + } + + static class LoadingValueReference implements ValueReference { + volatile ValueReference oldValue; + + // TODO(fry): rename get, then extend AbstractFuture instead of containing SettableFuture + final SettableFuture futureValue = SettableFuture.create(); + final Stopwatch stopwatch = new Stopwatch(); + + public LoadingValueReference() { + this(LocalCache.unset()); + } + + public LoadingValueReference(ValueReference oldValue) { + this.oldValue = oldValue; + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public boolean isActive() { + return oldValue.isActive(); + } + + @Override + public int getWeight() { + return oldValue.getWeight(); + } + + public boolean set(@Nullable V newValue) { + return futureValue.set(newValue); + } + + public boolean setException(Throwable t) { + return setException(futureValue, t); + } + + private static boolean setException(SettableFuture future, Throwable t) { + try { + return future.setException(t); + } catch (Error e) { + // the error will already be propagated by the loading thread + return false; + } + } + + private ListenableFuture fullyFailedFuture(Throwable t) { + SettableFuture future = SettableFuture.create(); + setException(future, t); + return future; + } + + @Override + public void notifyNewValue(@Nullable V newValue) { + if (newValue != null) { + // The pending load was clobbered by a manual write. + // Unblock all pending gets, and have them return the new value. + set(newValue); + } else { + // The pending load was removed. Delay notifications until loading completes. + oldValue = unset(); + } + + // TODO(fry): could also cancel loading if we had a handle on its future + } + + public ListenableFuture loadFuture(K key, CacheLoader loader) { + stopwatch.start(); + V previousValue = oldValue.get(); + try { + if (previousValue == null) { + V newValue = loader.load(key); + return set(newValue) ? futureValue : Futures.immediateFuture(newValue); + } else { + ListenableFuture newValue = loader.reload(key, previousValue); + // rely on loadAsync to call set in order to avoid adding a second listener here + return newValue != null ? newValue : Futures.immediateFuture(null); + } + } catch (Throwable t) { + if (t instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + return setException(t) ? futureValue : fullyFailedFuture(t); + } + } + + public long elapsedNanos() { + return stopwatch.elapsedTime(NANOSECONDS); + } + + @Override + public V waitForValue() throws ExecutionException { + return getUninterruptibly(futureValue); + } + + @Override + public V get() { + return oldValue.get(); + } + + public ValueReference getOldValue() { + return oldValue; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry) { + return this; + } + } + + // Queues + + /** + * A custom queue for managing eviction order. Note that this is tightly integrated with {@code + * ReferenceEntry}, upon which it relies to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in + * the map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle + * of the queue as part of copyWriteEntry, and (2) the contains method is highly optimized + * for the current model. + */ + static final class WriteQueue extends AbstractQueue> { + final ReferenceEntry head = new AbstractReferenceEntry() { + + @Override + public long getWriteTime() { + return Long.MAX_VALUE; + } + + @Override + public void setWriteTime(long time) {} + + ReferenceEntry nextWrite = this; + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + ReferenceEntry previousWrite = this; + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue()); + + // add to tail + connectWriteOrder(head.getPreviousInWriteQueue(), entry); + connectWriteOrder(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextInWriteQueue(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextInWriteQueue(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousInWriteQueue(); + ReferenceEntry next = e.getNextInWriteQueue(); + connectWriteOrder(previous, next); + nullifyWriteOrder(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextInWriteQueue() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextInWriteQueue() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextInWriteQueue(); e != head; + e = e.getNextInWriteQueue()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextInWriteQueue(); + while (e != head) { + ReferenceEntry next = e.getNextInWriteQueue(); + nullifyWriteOrder(e); + e = next; + } + + head.setNextInWriteQueue(head); + head.setPreviousInWriteQueue(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextInWriteQueue(); + return (next == head) ? null : next; + } + }; + } + } + + /** + * A custom queue for managing access order. Note that this is tightly integrated with + * {@code ReferenceEntry}, upon which it reliese to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in + * the map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle + * of the queue as part of copyWriteEntry, and (2) the contains method is highly optimized + * for the current model. + */ + static final class AccessQueue extends AbstractQueue> { + final ReferenceEntry head = new AbstractReferenceEntry() { + + @Override + public long getAccessTime() { + return Long.MAX_VALUE; + } + + @Override + public void setAccessTime(long time) {} + + ReferenceEntry nextAccess = this; + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + ReferenceEntry previousAccess = this; + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectAccessOrder(entry.getPreviousInAccessQueue(), entry.getNextInAccessQueue()); + + // add to tail + connectAccessOrder(head.getPreviousInAccessQueue(), entry); + connectAccessOrder(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextInAccessQueue(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextInAccessQueue(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousInAccessQueue(); + ReferenceEntry next = e.getNextInAccessQueue(); + connectAccessOrder(previous, next); + nullifyAccessOrder(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextInAccessQueue() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextInAccessQueue() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextInAccessQueue(); e != head; + e = e.getNextInAccessQueue()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextInAccessQueue(); + while (e != head) { + ReferenceEntry next = e.getNextInAccessQueue(); + nullifyAccessOrder(e); + e = next; + } + + head.setNextInAccessQueue(head); + head.setPreviousInAccessQueue(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextInAccessQueue(); + return (next == head) ? null : next; + } + }; + } + } + + // Cache support + + public void cleanUp() { + for (Segment segment : segments) { + segment.cleanUp(); + } + } + + // ConcurrentMap methods + + @Override + public boolean isEmpty() { + /* + * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and + * removed in one segment while checking another, in which case the table was never actually + * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment + * modifications before recheck.) Method containsValue() uses similar constructions for + * stability checks. + */ + long sum = 0L; + Segment[] segments = this.segments; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum += segments[i].modCount; + } + + if (sum != 0L) { // recheck unless no modifications + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum -= segments[i].modCount; + } + if (sum != 0L) { + return false; + } + } + return true; + } + + long longSize() { + Segment[] segments = this.segments; + long sum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + } + return sum; + } + + @Override + public int size() { + return Ints.saturatedCast(longSize()); + } + + @Override + @Nullable + public V get(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).get(key, hash); + } + + @Nullable + public V getIfPresent(Object key) { + int hash = hash(checkNotNull(key)); + V value = segmentFor(hash).get(key, hash); + if (value == null) { + globalStatsCounter.recordMisses(1); + } else { + globalStatsCounter.recordHits(1); + } + return value; + } + + V get(K key, CacheLoader loader) throws ExecutionException { + int hash = hash(checkNotNull(key)); + return segmentFor(hash).get(key, hash, loader); + } + + V getOrLoad(K key) throws ExecutionException { + return get(key, defaultLoader); + } + + ImmutableMap getAllPresent(Iterable keys) { + int hits = 0; + int misses = 0; + + Map result = Maps.newLinkedHashMap(); + for (Object key : keys) { + V value = get(key); + if (value == null) { + misses++; + } else { + // TODO(fry): store entry key instead of query key + @SuppressWarnings("unchecked") + K castKey = (K) key; + result.put(castKey, value); + hits++; + } + } + globalStatsCounter.recordHits(hits); + globalStatsCounter.recordMisses(misses); + return ImmutableMap.copyOf(result); + } + + ImmutableMap getAll(Iterable keys) throws ExecutionException { + int hits = 0; + int misses = 0; + + Map result = Maps.newLinkedHashMap(); + Set keysToLoad = Sets.newLinkedHashSet(); + for (K key : keys) { + V value = get(key); + if (!result.containsKey(key)) { + result.put(key, value); + if (value == null) { + misses++; + keysToLoad.add(key); + } else { + hits++; + } + } + } + + try { + if (!keysToLoad.isEmpty()) { + try { + Map newEntries = loadAll(keysToLoad, defaultLoader); + for (K key : keysToLoad) { + V value = newEntries.get(key); + if (value == null) { + throw new InvalidCacheLoadException("loadAll failed to return a value for " + key); + } + result.put(key, value); + } + } catch (UnsupportedLoadingOperationException e) { + // loadAll not implemented, fallback to load + for (K key : keysToLoad) { + misses--; // get will count this miss + result.put(key, get(key, defaultLoader)); + } + } + } + return ImmutableMap.copyOf(result); + } finally { + globalStatsCounter.recordHits(hits); + globalStatsCounter.recordMisses(misses); + } + } + + /** + * Returns the result of calling {@link CacheLoader#loadAll}, or null if {@code loader} doesn't + * implement {@code loadAll}. + */ + @Nullable + Map loadAll(Set keys, CacheLoader loader) + throws ExecutionException { + Stopwatch stopwatch = new Stopwatch().start(); + Map result; + boolean success = false; + try { + @SuppressWarnings("unchecked") // safe since all keys extend K + Map map = (Map) loader.loadAll(keys); + result = map; + success = true; + } catch (UnsupportedLoadingOperationException e) { + success = true; + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ExecutionException(e); + } catch (RuntimeException e) { + throw new UncheckedExecutionException(e); + } catch (Exception e) { + throw new ExecutionException(e); + } catch (Error e) { + throw new ExecutionError(e); + } finally { + if (!success) { + globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS)); + } + } + + if (result == null) { + globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS)); + throw new InvalidCacheLoadException(loader + " returned null map from loadAll"); + } + + stopwatch.stop(); + // TODO(fry): batch by segment + boolean nullsPresent = false; + for (Map.Entry entry : result.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + // delay failure until non-null entries are stored + nullsPresent = true; + } else { + put(key, value); + } + } + + if (nullsPresent) { + globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS)); + throw new InvalidCacheLoadException(loader + " returned null keys or values from loadAll"); + } + + // TODO(fry): record count of loaded entries + globalStatsCounter.recordLoadSuccess(stopwatch.elapsedTime(NANOSECONDS)); + return result; + } + + /** + * Returns the internal entry for the specified key. The entry may be loading, expired, or + * partially collected. + */ + ReferenceEntry getEntry(@Nullable Object key) { + // does not impact recency ordering + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getEntry(key, hash); + } + + /** + * Returns the live internal entry for the specified key. + */ + ReferenceEntry getLiveEntry(@Nullable Object key) { + // does not impact recency ordering + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getLiveEntry(key, hash, ticker.read()); + } + + void refresh(K key) { + int hash = hash(checkNotNull(key)); + segmentFor(hash).refresh(key, hash, defaultLoader); + } + + @Override + public boolean containsKey(@Nullable Object key) { + // does not impact recency ordering + if (key == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).containsKey(key, hash); + } + + @Override + public boolean containsValue(@Nullable Object value) { + // does not impact recency ordering + if (value == null) { + return false; + } + + // This implementation is patterned after ConcurrentHashMap, but without the locking. The only + // way for it to return a false negative would be for the target value to jump around in the map + // such that none of the subsequent iterations observed it, despite the fact that at every point + // in time it was present somewhere int the map. This becomes increasingly unlikely as + // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. + long now = ticker.read(); + final Segment[] segments = this.segments; + long last = -1L; + for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { + long sum = 0L; + for (Segment segment : segments) { + // ensure visibility of most recent completed write + @SuppressWarnings({"UnusedDeclaration", "unused"}) + int c = segment.count; // read-volatile + + AtomicReferenceArray> table = segment.table; + for (int j = 0 ; j < table.length(); j++) { + for (ReferenceEntry e = table.get(j); e != null; e = e.getNext()) { + V v = segment.getLiveValue(e, now); + if (v != null && valueEquivalence.equivalent(value, v)) { + return true; + } + } + } + sum += segment.modCount; + } + if (sum == last) { + break; + } + last = sum; + } + return false; + } + + @Override + public V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, false); + } + + @Override + public V putIfAbsent(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, true); + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + @Override + public V remove(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash); + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + if (key == null || value == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash, value); + } + + @Override + public boolean replace(K key, @Nullable V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(newValue); + if (oldValue == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).replace(key, hash, value); + } + + @Override + public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + } + + void invalidateAll(Iterable keys) { + // TODO(fry): batch by segment + for (Object key : keys) { + remove(key); + } + } + + Set keySet; + + @Override + public Set keySet() { + // does not impact recency ordering + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet()); + } + + Collection values; + + @Override + public Collection values() { + // does not impact recency ordering + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + Set> entrySet; + + @Override + public Set> entrySet() { + // does not impact recency ordering + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + // Iterator Support + + abstract class HashIterator { + + int nextSegmentIndex; + int nextTableIndex; + Segment currentSegment; + AtomicReferenceArray> currentTable; + ReferenceEntry nextEntry; + WriteThroughEntry nextExternal; + WriteThroughEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + final void advance() { + nextExternal = null; + + if (nextInChain()) { + return; + } + + if (nextInTable()) { + return; + } + + while (nextSegmentIndex >= 0) { + currentSegment = segments[nextSegmentIndex--]; + if (currentSegment.count != 0) { + currentTable = currentSegment.table; + nextTableIndex = currentTable.length() - 1; + if (nextInTable()) { + return; + } + } + } + } + + /** + * Finds the next entry in the current chain. Returns true if an entry was found. + */ + boolean nextInChain() { + if (nextEntry != null) { + for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) { + if (advanceTo(nextEntry)) { + return true; + } + } + } + return false; + } + + /** + * Finds the next entry in the current table. Returns true if an entry was found. + */ + boolean nextInTable() { + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { + if (advanceTo(nextEntry) || nextInChain()) { + return true; + } + } + } + return false; + } + + /** + * Advances to the given entry. Returns true if the entry was valid, false if it should be + * skipped. + */ + boolean advanceTo(ReferenceEntry entry) { + try { + long now = ticker.read(); + K key = entry.getKey(); + V value = getLiveValue(entry, now); + if (value != null) { + nextExternal = new WriteThroughEntry(key, value); + return true; + } else { + // Skip stale entry. + return false; + } + } finally { + currentSegment.postReadCleanup(); + } + } + + public boolean hasNext() { + return nextExternal != null; + } + + WriteThroughEntry nextEntry() { + if (nextExternal == null) { + throw new NoSuchElementException(); + } + lastReturned = nextExternal; + advance(); + return lastReturned; + } + + public void remove() { + checkState(lastReturned != null); + LocalCache.this.remove(lastReturned.getKey()); + lastReturned = null; + } + } + + final class KeyIterator extends HashIterator implements Iterator { + + @Override + public K next() { + return nextEntry().getKey(); + } + } + + final class ValueIterator extends HashIterator implements Iterator { + + @Override + public V next() { + return nextEntry().getValue(); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the + * underlying map. + */ + final class WriteThroughEntry implements Entry { + final K key; // non-null + V value; // non-null + + WriteThroughEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(@Nullable Object object) { + // Cannot use key and value equivalence + if (object instanceof Entry) { + Entry that = (Entry) object; + return key.equals(that.getKey()) && value.equals(that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + // Cannot use key and value equivalence + return key.hashCode() ^ value.hashCode(); + } + + @Override + public V setValue(V newValue) { + throw new UnsupportedOperationException(); + } + + /** + * Returns a string representation of the form {key}={value}. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } + } + + final class EntryIterator extends HashIterator implements Iterator> { + + @Override + public Entry next() { + return nextEntry(); + } + } + + final class KeySet extends AbstractSet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return LocalCache.this.size(); + } + + @Override + public boolean isEmpty() { + return LocalCache.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return LocalCache.this.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return LocalCache.this.remove(o) != null; + } + + @Override + public void clear() { + LocalCache.this.clear(); + } + } + + final class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return LocalCache.this.size(); + } + + @Override + public boolean isEmpty() { + return LocalCache.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return LocalCache.this.containsValue(o); + } + + @Override + public void clear() { + LocalCache.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + if (key == null) { + return false; + } + V v = LocalCache.this.get(key); + + return v != null && valueEquivalence.equivalent(e.getValue(), v); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + return key != null && LocalCache.this.remove(key, e.getValue()); + } + + @Override + public int size() { + return LocalCache.this.size(); + } + + @Override + public boolean isEmpty() { + return LocalCache.this.isEmpty(); + } + + @Override + public void clear() { + LocalCache.this.clear(); + } + } + + // Serialization Support + + /** + * Serializes the configuration of a LocalCache, reconsitituting it as a Cache using + * CacheBuilder upon deserialization. An instance of this class is fit for use by the writeReplace + * of LocalManualCache. + * + * Unfortunately, readResolve() doesn't get called when a circular dependency is present, so the + * proxy must be able to behave as the cache itself. + */ + static class ManualSerializationProxy + extends ForwardingCache implements Serializable { + private static final long serialVersionUID = 1; + + final Strength keyStrength; + final Strength valueStrength; + final Equivalence keyEquivalence; + final Equivalence valueEquivalence; + final long expireAfterWriteNanos; + final long expireAfterAccessNanos; + final long maxWeight; + final Weigher weigher; + final int concurrencyLevel; + final RemovalListener removalListener; + final Ticker ticker; + final CacheLoader loader; + + transient Cache delegate; + + ManualSerializationProxy(LocalCache cache) { + this( + cache.keyStrength, + cache.valueStrength, + cache.keyEquivalence, + cache.valueEquivalence, + cache.expireAfterWriteNanos, + cache.expireAfterAccessNanos, + cache.maxWeight, + cache.weigher, + cache.concurrencyLevel, + cache.removalListener, + cache.ticker, + cache.defaultLoader); + } + + private ManualSerializationProxy( + Strength keyStrength, Strength valueStrength, + Equivalence keyEquivalence, Equivalence valueEquivalence, + long expireAfterWriteNanos, long expireAfterAccessNanos, long maxWeight, + Weigher weigher, int concurrencyLevel, + RemovalListener removalListener, + Ticker ticker, CacheLoader loader) { + this.keyStrength = keyStrength; + this.valueStrength = valueStrength; + this.keyEquivalence = keyEquivalence; + this.valueEquivalence = valueEquivalence; + this.expireAfterWriteNanos = expireAfterWriteNanos; + this.expireAfterAccessNanos = expireAfterAccessNanos; + this.maxWeight = maxWeight; + this.weigher = weigher; + this.concurrencyLevel = concurrencyLevel; + this.removalListener = removalListener; + this.ticker = (ticker == Ticker.systemTicker() || ticker == NULL_TICKER) + ? null : ticker; + this.loader = loader; + } + + CacheBuilder recreateCacheBuilder() { + CacheBuilder builder = CacheBuilder.newBuilder() + .setKeyStrength(keyStrength) + .setValueStrength(valueStrength) + .keyEquivalence(keyEquivalence) + .valueEquivalence(valueEquivalence) + .concurrencyLevel(concurrencyLevel); + builder.strictParsing = false; + builder.removalListener(removalListener); + if (expireAfterWriteNanos > 0) { + builder.expireAfterWrite(expireAfterWriteNanos, TimeUnit.NANOSECONDS); + } + if (expireAfterAccessNanos > 0) { + builder.expireAfterAccess(expireAfterAccessNanos, TimeUnit.NANOSECONDS); + } + if (weigher != OneWeigher.INSTANCE) { + builder.weigher(weigher); + if (maxWeight != UNSET_INT) { + builder.maximumWeight(maxWeight); + } + } else { + if (maxWeight != UNSET_INT) { + builder.maximumSize(maxWeight); + } + } + if (ticker != null) { + builder.ticker(ticker); + } + return builder; + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + CacheBuilder builder = recreateCacheBuilder(); + this.delegate = builder.build(); + } + + private Object readResolve() { + return delegate; + } + + @Override + protected Cache delegate() { + return delegate; + } + } + + /** + * Serializes the configuration of a LocalCache, reconsitituting it as an LoadingCache using + * CacheBuilder upon deserialization. An instance of this class is fit for use by the writeReplace + * of LocalLoadingCache. + * + * Unfortunately, readResolve() doesn't get called when a circular dependency is present, so the + * proxy must be able to behave as the cache itself. + */ + static final class LoadingSerializationProxy + extends ManualSerializationProxy implements LoadingCache, Serializable { + private static final long serialVersionUID = 1; + + transient LoadingCache autoDelegate; + + LoadingSerializationProxy(LocalCache cache) { + super(cache); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + CacheBuilder builder = recreateCacheBuilder(); + this.autoDelegate = builder.build(loader); + } + + @Override + public V get(K key) throws ExecutionException { + return autoDelegate.get(key); + } + + @Override + public V getUnchecked(K key) { + return autoDelegate.getUnchecked(key); + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return autoDelegate.getAll(keys); + } + + @Override + public final V apply(K key) { + return autoDelegate.apply(key); + } + + @Override + public void refresh(K key) { + autoDelegate.refresh(key); + } + + private Object readResolve() { + return autoDelegate; + } + } + + static class LocalManualCache implements Cache, Serializable { + final LocalCache localCache; + + LocalManualCache(CacheBuilder builder) { + this(new LocalCache(builder, null)); + } + + private LocalManualCache(LocalCache localCache) { + this.localCache = localCache; + } + + // Cache methods + + @Override + @Nullable + public V getIfPresent(Object key) { + return localCache.getIfPresent(key); + } + + @Override + public V get(K key, final Callable valueLoader) throws ExecutionException { + checkNotNull(valueLoader); + return localCache.get(key, new CacheLoader() { + @Override + public V load(Object key) throws Exception { + return valueLoader.call(); + } + }); + } + + @Override + public ImmutableMap getAllPresent(Iterable keys) { + return localCache.getAllPresent(keys); + } + + @Override + public void put(K key, V value) { + localCache.put(key, value); + } + + @Override + public void putAll(Map m) { + localCache.putAll(m); + } + + @Override + public void invalidate(Object key) { + checkNotNull(key); + localCache.remove(key); + } + + @Override + public void invalidateAll(Iterable keys) { + localCache.invalidateAll(keys); + } + + @Override + public void invalidateAll() { + localCache.clear(); + } + + @Override + public long size() { + return localCache.longSize(); + } + + @Override + public ConcurrentMap asMap() { + return localCache; + } + + @Override + public CacheStats stats() { + SimpleStatsCounter aggregator = new SimpleStatsCounter(); + aggregator.incrementBy(localCache.globalStatsCounter); + for (Segment segment : localCache.segments) { + aggregator.incrementBy(segment.statsCounter); + } + return aggregator.snapshot(); + } + + @Override + public void cleanUp() { + localCache.cleanUp(); + } + + // Serialization Support + + private static final long serialVersionUID = 1; + + Object writeReplace() { + return new ManualSerializationProxy(localCache); + } + } + + static class LocalLoadingCache + extends LocalManualCache implements LoadingCache { + + LocalLoadingCache(CacheBuilder builder, + CacheLoader loader) { + super(new LocalCache(builder, checkNotNull(loader))); + } + + // LoadingCache methods + + @Override + public V get(K key) throws ExecutionException { + return localCache.getOrLoad(key); + } + + @Override + public V getUnchecked(K key) { + try { + return get(key); + } catch (ExecutionException e) { + throw new UncheckedExecutionException(e.getCause()); + } + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return localCache.getAll(keys); + } + + @Override + public void refresh(K key) { + localCache.refresh(key); + } + + @Override + public final V apply(K key) { + return getUnchecked(key); + } + + // Serialization Support + + private static final long serialVersionUID = 1; + + Object writeReplace() { + return new LoadingSerializationProxy(localCache); + } + } +} diff --git a/guava/src/com/google/common/cache/LongAdder.java b/guava/src/com/google/common/cache/LongAdder.java new file mode 100644 index 0000000..7838191 --- /dev/null +++ b/guava/src/com/google/common/cache/LongAdder.java @@ -0,0 +1,207 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8 + */ + +package com.google.common.cache; +import java.util.concurrent.atomic.AtomicLong; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic + * + * @since 1.8 + * @author Doug Lea + */ +final class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} diff --git a/guava/src/com/google/common/cache/RemovalCause.java b/guava/src/com/google/common/cache/RemovalCause.java new file mode 100644 index 0000000..6574b0e --- /dev/null +++ b/guava/src/com/google/common/cache/RemovalCause.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * The reason why a cached entry was removed. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +public enum RemovalCause { + /** + * The entry was manually removed by the user. This can result from the user invoking + * {@link Cache#invalidate}, {@link Cache#invalidateAll(Iterable)}, {@link Cache#invalidateAll()}, + * {@link Map#remove}, {@link ConcurrentMap#remove}, or {@link Iterator#remove}. + */ + EXPLICIT { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry itself was not actually removed, but its value was replaced by the user. This can + * result from the user invoking {@link Cache#put}, {@link LoadingCache#refresh}, {@link Map#put}, + * {@link Map#putAll}, {@link ConcurrentMap#replace(Object, Object)}, or + * {@link ConcurrentMap#replace(Object, Object, Object)}. + */ + REPLACED { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry was removed automatically because its key or value was garbage-collected. This + * can occur when using {@link CacheBuilder#weakKeys}, {@link CacheBuilder#weakValues}, or + * {@link CacheBuilder#softValues}. + */ + COLLECTED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry's expiration timestamp has passed. This can occur when using + * {@link CacheBuilder#expireAfterWrite} or {@link CacheBuilder#expireAfterAccess}. + */ + EXPIRED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry was evicted due to size constraints. This can occur when using + * {@link CacheBuilder#maximumSize} or {@link CacheBuilder#maximumWeight}. + */ + SIZE { + @Override + boolean wasEvicted() { + return true; + } + }; + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link #EXPLICIT} nor {@link #REPLACED}). + */ + abstract boolean wasEvicted(); +} diff --git a/guava/src/com/google/common/cache/RemovalListener.java b/guava/src/com/google/common/cache/RemovalListener.java new file mode 100644 index 0000000..e9b6c2c --- /dev/null +++ b/guava/src/com/google/common/cache/RemovalListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; + +/** + * An object that can receive a notification when an entry is removed from a cache. The removal + * resulting in notification could have occured to an entry being manually removed or replaced, or + * due to eviction resulting from timed expiration, exceeding a maximum size, or garbage + * collection. + * + *

An instance may be called concurrently by multiple threads to process different entries. + * Implementations of this interface should avoid performing blocking calls or synchronizing on + * shared resources. + * + * @param the most general type of keys this listener can listen for; for + * example {@code Object} if any key is acceptable + * @param the most general type of values this listener can listen for; for + * example {@code Object} if any key is acceptable + * @author Charles Fry + * @since 10.0 + */ +@Beta +public interface RemovalListener { + /** + * Notifies the listener that a removal occurred at some point in the past. + */ + // Technically should accept RemovalNotification, but because + // RemovalNotification is guaranteed covariant, let's make users' lives simpler. + void onRemoval(RemovalNotification notification); +} diff --git a/guava/src/com/google/common/cache/RemovalListeners.java b/guava/src/com/google/common/cache/RemovalListeners.java new file mode 100644 index 0000000..18292fd --- /dev/null +++ b/guava/src/com/google/common/cache/RemovalListeners.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.Executor; + +/** + * A collection of common removal listeners. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +public final class RemovalListeners { + + private RemovalListeners() {} + + /** + * Returns a {@code RemovalListener} which processes all eviction + * notifications using {@code executor}. + * + * @param listener the backing listener + * @param executor the executor with which removal notifications are + * asynchronously executed + */ + public static RemovalListener asynchronous( + final RemovalListener listener, final Executor executor) { + return new RemovalListener() { + @Override + public void onRemoval(final RemovalNotification notification) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.onRemoval(notification); + } + }); + } + }; + } + +} diff --git a/guava/src/com/google/common/cache/RemovalNotification.java b/guava/src/com/google/common/cache/RemovalNotification.java new file mode 100644 index 0000000..8e0066e --- /dev/null +++ b/guava/src/com/google/common/cache/RemovalNotification.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; + +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * A notification of the removal of a single entry. The key and/or value may be null if they were + * already garbage collected. + * + *

Like other {@code Map.Entry} instances associated with {@code CacheBuilder}, this class holds + * strong references to the key and value, regardless of the type of references the cache may be + * using. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +public final class RemovalNotification implements Entry { + @Nullable private final K key; + @Nullable private final V value; + private final RemovalCause cause; + + RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) { + this.key = key; + this.value = value; + this.cause = checkNotNull(cause); + } + + /** + * Returns the cause for which the entry was removed. + */ + public RemovalCause getCause() { + return cause; + } + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}). + */ + public boolean wasEvicted() { + return cause.wasEvicted(); + } + + @Nullable @Override public K getKey() { + return key; + } + + @Nullable @Override public V getValue() { + return value; + } + + @Override public final V setValue(V value){ + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + @Override public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form {key}={value}. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/cache/Striped64.java b/guava/src/com/google/common/cache/Striped64.java new file mode 100644 index 0000000..c1ba850 --- /dev/null +++ b/guava/src/com/google/common/cache/Striped64.java @@ -0,0 +1,344 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/SequenceLock.java?revision=1.17 + */ + +package com.google.common.cache; +import java.util.Random; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long baseOffset; + private static final long busyOffset; + static { + try { + UNSAFE = getUnsafe(); + Class sk = Striped64.class; + baseOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + busyOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("busy")); + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } + +} diff --git a/guava/src/com/google/common/cache/Weigher.java b/guava/src/com/google/common/cache/Weigher.java new file mode 100644 index 0000000..4506f9e --- /dev/null +++ b/guava/src/com/google/common/cache/Weigher.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.Beta; + +/** + * Calculates the weights of cache entries. + * + * @author Charles Fry + * @since 11.0 + */ +@Beta +public interface Weigher { + + /** + * Returns the weight of a cache entry. There is no unit for entry weights; rather they are simply + * relative to each other. + * + * @return the weight of the entry; must be non-negative + */ + int weigh(K key, V value); +} diff --git a/guava/src/com/google/common/cache/package-info.java b/guava/src/com/google/common/cache/package-info.java new file mode 100644 index 0000000..ea0297b --- /dev/null +++ b/guava/src/com/google/common/cache/package-info.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains caching utilities. + * + *

The core interface used to represent caches is {@link com.google.common.cache.Cache}. + * In-memory caches can be configured and created using + * {@link com.google.common.cache.CacheBuilder}, with cache entries being loaded by + * {@link com.google.common.cache.CacheLoader}. Statistics about cache performance are exposed using + * {@link com.google.common.cache.CacheStats}. + * + *

See the Guava User Guide article on caches. + * + *

This package is a part of the open-source + * Guava libraries. + * + * @author Charles Fry + */ +@ParametersAreNonnullByDefault +package com.google.common.cache; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/guava/src/com/google/common/collect/AbstractBiMap.java b/guava/src/com/google/common/collect/AbstractBiMap.java new file mode 100644 index 0000000..dcf2db4 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractBiMap.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A general-purpose bimap implementation using any two backing {@code Map} + * instances. + * + *

Note that this class contains {@code equals()} calls that keep it from + * supporting {@code IdentityHashMap} backing maps. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +@GwtCompatible(emulated = true) +abstract class AbstractBiMap extends ForwardingMap + implements BiMap, Serializable { + + private transient Map delegate; + transient AbstractBiMap inverse; + + /** Package-private constructor for creating a map-backed bimap. */ + AbstractBiMap(Map forward, Map backward) { + setDelegates(forward, backward); + } + + /** Private constructor for inverse bimap. */ + private AbstractBiMap(Map backward, AbstractBiMap forward) { + delegate = backward; + inverse = forward; + } + + @Override protected Map delegate() { + return delegate; + } + + /** + * Returns its input, or throws an exception if this is not a valid key. + */ + K checkKey(@Nullable K key) { + return key; + } + + /** + * Returns its input, or throws an exception if this is not a valid value. + */ + V checkValue(@Nullable V value) { + return value; + } + + /** + * Specifies the delegate maps going in each direction. Called by the + * constructor and by subclasses during deserialization. + */ + void setDelegates(Map forward, Map backward) { + checkState(delegate == null); + checkState(inverse == null); + checkArgument(forward.isEmpty()); + checkArgument(backward.isEmpty()); + checkArgument(forward != backward); + delegate = forward; + inverse = new Inverse(backward, this); + } + + void setInverse(AbstractBiMap inverse) { + this.inverse = inverse; + } + + // Query Operations (optimizations) + + @Override public boolean containsValue(Object value) { + return inverse.containsKey(value); + } + + // Modification Operations + + @Override public V put(K key, V value) { + return putInBothMaps(key, value, false); + } + + @Override + public V forcePut(K key, V value) { + return putInBothMaps(key, value, true); + } + + private V putInBothMaps(@Nullable K key, @Nullable V value, boolean force) { + checkKey(key); + checkValue(value); + boolean containedKey = containsKey(key); + if (containedKey && Objects.equal(value, get(key))) { + return value; + } + if (force) { + inverse().remove(value); + } else { + checkArgument(!containsValue(value), "value already present: %s", value); + } + V oldValue = delegate.put(key, value); + updateInverseMap(key, containedKey, oldValue, value); + return oldValue; + } + + private void updateInverseMap( + K key, boolean containedKey, V oldValue, V newValue) { + if (containedKey) { + removeFromInverseMap(oldValue); + } + inverse.delegate.put(newValue, key); + } + + @Override public V remove(Object key) { + return containsKey(key) ? removeFromBothMaps(key) : null; + } + + private V removeFromBothMaps(Object key) { + V oldValue = delegate.remove(key); + removeFromInverseMap(oldValue); + return oldValue; + } + + private void removeFromInverseMap(V oldValue) { + inverse.delegate.remove(oldValue); + } + + // Bulk Operations + + @Override public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override public void clear() { + delegate.clear(); + inverse.delegate.clear(); + } + + // Views + + @Override + public BiMap inverse() { + return inverse; + } + + private transient Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = new KeySet() : result; + } + + private class KeySet extends ForwardingSet { + @Override protected Set delegate() { + return delegate.keySet(); + } + + @Override public void clear() { + AbstractBiMap.this.clear(); + } + + @Override public boolean remove(Object key) { + if (!contains(key)) { + return false; + } + removeFromBothMaps(key); + return true; + } + + @Override public boolean removeAll(Collection keysToRemove) { + return standardRemoveAll(keysToRemove); + } + + @Override public boolean retainAll(Collection keysToRetain) { + return standardRetainAll(keysToRetain); + } + + @Override public Iterator iterator() { + return Maps.keyIterator(entrySet().iterator()); + } + } + + private transient Set valueSet; + + @Override public Set values() { + /* + * We can almost reuse the inverse's keySet, except we have to fix the + * iteration order so that it is consistent with the forward map. + */ + Set result = valueSet; + return (result == null) ? valueSet = new ValueSet() : result; + } + + private class ValueSet extends ForwardingSet { + final Set valuesDelegate = inverse.keySet(); + + @Override protected Set delegate() { + return valuesDelegate; + } + + @Override public Iterator iterator() { + return Maps.valueIterator(entrySet().iterator()); + } + + @Override public Object[] toArray() { + return standardToArray(); + } + + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override public String toString() { + return standardToString(); + } + } + + private transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends ForwardingSet> { + final Set> esDelegate = delegate.entrySet(); + + @Override protected Set> delegate() { + return esDelegate; + } + + @Override public void clear() { + AbstractBiMap.this.clear(); + } + + @Override public boolean remove(Object object) { + if (!esDelegate.contains(object)) { + return false; + } + + // safe because esDelgate.contains(object). + Entry entry = (Entry) object; + inverse.delegate.remove(entry.getValue()); + /* + * Remove the mapping in inverse before removing from esDelegate because + * if entry is part of esDelegate, entry might be invalidated after the + * mapping is removed from esDelegate. + */ + esDelegate.remove(entry); + return true; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = esDelegate.iterator(); + return new Iterator>() { + Entry entry; + + @Override public boolean hasNext() { + return iterator.hasNext(); + } + + @Override public Entry next() { + entry = iterator.next(); + final Entry finalEntry = entry; + + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return finalEntry; + } + + @Override public V setValue(V value) { + // Preconditions keep the map and inverse consistent. + checkState(contains(this), "entry no longer in map"); + // similar to putInBothMaps, but set via entry + if (Objects.equal(value, getValue())) { + return value; + } + checkArgument(!containsValue(value), + "value already present: %s", value); + V oldValue = finalEntry.setValue(value); + checkState(Objects.equal(value, get(getKey())), + "entry no longer in map"); + updateInverseMap(getKey(), true, oldValue, value); + return oldValue; + } + }; + } + + @Override public void remove() { + checkState(entry != null); + V value = entry.getValue(); + iterator.remove(); + removeFromInverseMap(value); + } + }; + } + + // See java.util.Collections.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return standardToArray(); + } + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + @Override public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + @Override public boolean retainAll(Collection c) { + return standardRetainAll(c); + } + } + + /** The inverse of any other {@code AbstractBiMap} subclass. */ + private static class Inverse extends AbstractBiMap { + private Inverse(Map backward, AbstractBiMap forward) { + super(backward, forward); + } + + /* + * Serialization stores the forward bimap, the inverse of this inverse. + * Deserialization calls inverse() on the forward bimap and returns that + * inverse. + * + * If a bimap and its inverse are serialized together, the deserialized + * instances have inverse() methods that return the other. + */ + + @Override + K checkKey(K key) { + return inverse.checkValue(key); + } + + @Override + V checkValue(V value) { + return inverse.checkKey(value); + } + + /** + * @serialData the forward bimap + */ + @GwtIncompatible("java.io.ObjectOuputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(inverse()); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setInverse((AbstractBiMap) stream.readObject()); + } + + @GwtIncompatible("Not needed in the emulated source.") + Object readResolve() { + return inverse().inverse(); + } + + @GwtIncompatible("Not needed in emulated source.") + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("Not needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/AbstractIndexedListIterator.java b/guava/src/com/google/common/collect/AbstractIndexedListIterator.java new file mode 100644 index 0000000..a58a2a0 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractIndexedListIterator.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkPositionIndex; + +import com.google.common.annotations.GwtCompatible; + +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@link ListIterator} + * interface across a fixed number of elements that may be retrieved by + * position. It does not support {@link #remove}, {@link #set}, or {@link #add}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractIndexedListIterator + extends UnmodifiableListIterator { + private final int size; + private int position; + + /** + * Returns the element with the specified index. This method is called by + * {@link #next()}. + */ + protected abstract E get(int index); + + /** + * Constructs an iterator across a sequence of the given size whose initial + * position is 0. That is, the first call to {@link #next()} will return the + * first element (or throw {@link NoSuchElementException} if {@code size} is + * zero). + * + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size) { + this(size, 0); + } + + /** + * Constructs an iterator across a sequence of the given size with the given + * initial position. That is, the first call to {@link #nextIndex()} will + * return {@code position}, and the first call to {@link #next()} will return + * the element at that index, if available. Calls to {@link #previous()} can + * retrieve the preceding {@code position} elements. + * + * @throws IndexOutOfBoundsException if {@code position} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size, int position) { + checkPositionIndex(position, size); + this.size = size; + this.position = position; + } + + @Override + public final boolean hasNext() { + return position < size; + } + + @Override + public final E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return get(position++); + } + + @Override + public final int nextIndex() { + return position; + } + + @Override + public final boolean hasPrevious() { + return position > 0; + } + + @Override + public final E previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + return get(--position); + } + + @Override + public final int previousIndex() { + return position - 1; + } +} diff --git a/guava/src/com/google/common/collect/AbstractIterator.java b/guava/src/com/google/common/collect/AbstractIterator.java new file mode 100644 index 0000000..b81a69c --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractIterator.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@code Iterator} + * interface, to make this interface easier to implement for certain types of + * data sources. + * + *

{@code Iterator} requires its implementations to support querying the + * end-of-data status without changing the iterator's state, using the {@link + * #hasNext} method. But many data sources, such as {@link + * java.io.Reader#read()}, do not expose this information; the only way to + * discover whether there is any data left is by trying to retrieve it. These + * types of data sources are ordinarily difficult to write iterators for. But + * using this class, one must implement only the {@link #computeNext} method, + * and invoke the {@link #endOfData} method when appropriate. + * + *

Another example is an iterator that skips over null elements in a backing + * iterator. This could be implemented as:

   {@code
+ *
+ *   public static Iterator skipNulls(final Iterator in) {
+ *     return new AbstractIterator() {
+ *       protected String computeNext() {
+ *         while (in.hasNext()) {
+ *           String s = in.next();
+ *           if (s != null) {
+ *             return s;
+ *           }
+ *         }
+ *         return endOfData();
+ *       }
+ *     };
+ *   }}
+ * + * This class supports iterators that include null elements. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +// When making changes to this class, please also update the copy at +// com.google.common.base.AbstractIterator +@GwtCompatible +public abstract class AbstractIterator extends UnmodifiableIterator { + private State state = State.NOT_READY; + + /** Constructor for use by subclasses. */ + protected AbstractIterator() {} + + private enum State { + /** We have computed the next element and haven't returned it yet. */ + READY, + + /** We haven't yet computed or have already returned the element. */ + NOT_READY, + + /** We have reached the end of the data and are finished. */ + DONE, + + /** We've suffered an exception and are kaput. */ + FAILED, + } + + private T next; + + /** + * Returns the next element. Note: the implementation must call {@link + * #endOfData()} when there are no elements left in the iteration. Failure to + * do so could result in an infinite loop. + * + *

The initial invocation of {@link #hasNext()} or {@link #next()} calls + * this method, as does the first invocation of {@code hasNext} or {@code + * next} following each successful call to {@code next}. Once the + * implementation either invokes {@code endOfData} or throws an exception, + * {@code computeNext} is guaranteed to never be called again. + * + *

If this method throws an exception, it will propagate outward to the + * {@code hasNext} or {@code next} invocation that invoked this method. Any + * further attempts to use the iterator will result in an {@link + * IllegalStateException}. + * + *

The implementation of this method may not invoke the {@code hasNext}, + * {@code next}, or {@link #peek()} methods on this instance; if it does, an + * {@code IllegalStateException} will result. + * + * @return the next element if there was one. If {@code endOfData} was called + * during execution, the return value will be ignored. + * @throws RuntimeException if any unrecoverable error happens. This exception + * will propagate outward to the {@code hasNext()}, {@code next()}, or + * {@code peek()} invocation that invoked this method. Any further + * attempts to use the iterator will result in an + * {@link IllegalStateException}. + */ + protected abstract T computeNext(); + + /** + * Implementations of {@link #computeNext} must invoke this method when + * there are no elements left in the iteration. + * + * @return {@code null}; a convenience so your {@code computeNext} + * implementation can use the simple statement {@code return endOfData();} + */ + protected final T endOfData() { + state = State.DONE; + return null; + } + + @Override + public final boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + return next; + } + + /** + * Returns the next element in the iteration without advancing the iteration, + * according to the contract of {@link PeekingIterator#peek()}. + * + *

Implementations of {@code AbstractIterator} that wish to expose this + * functionality should implement {@code PeekingIterator}. + */ + public final T peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return next; + } +} diff --git a/guava/src/com/google/common/collect/AbstractLinkedIterator.java b/guava/src/com/google/common/collect/AbstractLinkedIterator.java new file mode 100644 index 0000000..8e722dd --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractLinkedIterator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * This class provides a skeletal implementation of the {@code Iterator} + * interface for sequences whose next element can always be derived from the + * previous element. Null elements are not supported, nor is the + * {@link #remove()} method. + * + *

Example:

   {@code
+ *
+ *   Iterator powersOfTwo = new AbstractLinkedIterator(1) {
+ *     protected Integer computeNext(Integer previous) {
+ *       return (previous == 1 << 30) ? null : previous * 2;
+ *     }
+ *   };}
+ * + * @author Chris Povirk + * @since 8.0 + * @deprecated This class has been renamed {@link AbstractSequentialIterator}. + * This class is scheduled to be removed in Guava release 13.0. + */ +@Beta +@Deprecated +@GwtCompatible +public abstract class AbstractLinkedIterator + extends UnmodifiableIterator { + private T nextOrNull; + + /** + * Creates a new iterator with the given first element, or, if {@code + * firstOrNull} is null, creates a new empty iterator. + */ + protected AbstractLinkedIterator(@Nullable T firstOrNull) { + this.nextOrNull = firstOrNull; + } + + /** + * Returns the element that follows {@code previous}, or returns {@code null} + * if no elements remain. This method is invoked during each call to + * {@link #next()} in order to compute the result of a future call to + * {@code next()}. + */ + protected abstract T computeNext(T previous); + + @Override + public final boolean hasNext() { + return nextOrNull != null; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return nextOrNull; + } finally { + nextOrNull = computeNext(nextOrNull); + } + } +} diff --git a/guava/src/com/google/common/collect/AbstractListMultimap.java b/guava/src/com/google/common/collect/AbstractListMultimap.java new file mode 100644 index 0000000..ad24011 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractListMultimap.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link ListMultimap} interface. It's a wrapper + * around {@link AbstractMultimap} that converts the returned collections into + * {@code Lists}. The {@link #createCollection} method must return a {@code + * List}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +abstract class AbstractListMultimap + extends AbstractMultimap implements ListMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractListMultimap(Map> map) { + super(map); + } + + @Override abstract List createCollection(); + + // Following Javadoc copied from ListMultimap. + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + */ + @Override public List get(@Nullable K key) { + return (List) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + */ + @Override public List removeAll(@Nullable Object key) { + return (List) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + */ + @Override public List replaceValues( + @Nullable K key, Iterable values) { + return (List) super.replaceValues(key, values); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + @Override public boolean put(@Nullable K key, @Nullable V value) { + return super.put(key, value); + } + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link List} values. + */ + @Override public Map> asMap() { + return super.asMap(); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + @Override public boolean equals(@Nullable Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 6588350623831699109L; +} diff --git a/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java b/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java new file mode 100644 index 0000000..83d1953 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Multisets.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Ints; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Basic implementation of {@code Multiset} backed by an instance of {@code + * Map}. + * + *

For serialization to work, the subclass must specify explicit {@code + * readObject} and {@code writeObject} methods. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class AbstractMapBasedMultiset extends AbstractMultiset + implements Serializable { + + private transient Map backingMap; + + /* + * Cache the size for efficiency. Using a long lets us avoid the need for + * overflow checking and ensures that size() will function correctly even if + * the multiset had once been larger than Integer.MAX_VALUE. + */ + private transient long size; + + /** Standard constructor. */ + protected AbstractMapBasedMultiset(Map backingMap) { + this.backingMap = checkNotNull(backingMap); + this.size = super.size(); + } + + Map backingMap() { + return backingMap; + } + + /** Used during deserialization only. The backing map must be empty. */ + void setBackingMap(Map backingMap) { + this.backingMap = backingMap; + } + + // Required Implementations + + /** + * {@inheritDoc} + * + *

Invoking {@link Multiset.Entry#getCount} on an entry in the returned + * set always returns the current count of that element in the multiset, as + * opposed to the count at the time the entry was retrieved. + */ + @Override + public Set> entrySet() { + return super.entrySet(); + } + + @Override + Iterator> entryIterator() { + final Iterator> backingEntries = + backingMap.entrySet().iterator(); + return new Iterator>() { + Map.Entry toRemove; + + @Override + public boolean hasNext() { + return backingEntries.hasNext(); + } + + @Override + public Multiset.Entry next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return new Multisets.AbstractEntry() { + @Override + public E getElement() { + return mapEntry.getKey(); + } + @Override + public int getCount() { + int count = mapEntry.getValue().get(); + if (count == 0) { + Count frequency = backingMap.get(getElement()); + if (frequency != null) { + count = frequency.get(); + } + } + return count; + } + }; + } + + @Override + public void remove() { + Iterators.checkRemove(toRemove != null); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override + public void clear() { + for (Count frequency : backingMap.values()) { + frequency.set(0); + } + backingMap.clear(); + size = 0L; + } + + @Override + int distinctElements() { + return backingMap.size(); + } + + // Optimizations - Query Operations + + @Override public int size() { + return Ints.saturatedCast(size); + } + + @Override public Iterator iterator() { + return new MapBasedMultisetIterator(); + } + + /* + * Not subclassing AbstractMultiset$MultisetIterator because next() needs to + * retrieve the Map.Entry entry, which can then be used for + * a more efficient remove() call. + */ + private class MapBasedMultisetIterator implements Iterator { + final Iterator> entryIterator; + Map.Entry currentEntry; + int occurrencesLeft; + boolean canRemove; + + MapBasedMultisetIterator() { + this.entryIterator = backingMap.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return occurrencesLeft > 0 || entryIterator.hasNext(); + } + + @Override + public E next() { + if (occurrencesLeft == 0) { + currentEntry = entryIterator.next(); + occurrencesLeft = currentEntry.getValue().get(); + } + occurrencesLeft--; + canRemove = true; + return currentEntry.getKey(); + } + + @Override + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + int frequency = currentEntry.getValue().get(); + if (frequency <= 0) { + throw new ConcurrentModificationException(); + } + if (currentEntry.getValue().addAndGet(-1) == 0) { + entryIterator.remove(); + } + size--; + canRemove = false; + } + } + + @Override public int count(@Nullable Object element) { + try { + Count frequency = backingMap.get(element); + return (frequency == null) ? 0 : frequency.get(); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + // Optional Operations - Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the call would result in more than + * {@link Integer#MAX_VALUE} occurrences of {@code element} in this + * multiset. + */ + @Override public int add(@Nullable E element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + int oldCount; + if (frequency == null) { + oldCount = 0; + backingMap.put(element, new Count(occurrences)); + } else { + oldCount = frequency.get(); + long newCount = (long) oldCount + (long) occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, + "too many occurrences: %s", newCount); + frequency.getAndAdd(occurrences); + } + size += occurrences; + return oldCount; + } + + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + if (frequency == null) { + return 0; + } + + int oldCount = frequency.get(); + + int numberRemoved; + if (oldCount > occurrences) { + numberRemoved = occurrences; + } else { + numberRemoved = oldCount; + backingMap.remove(element); + } + + frequency.addAndGet(-numberRemoved); + size -= numberRemoved; + return oldCount; + } + + // Roughly a 33% performance improvement over AbstractMultiset.setCount(). + @Override public int setCount(@Nullable E element, int count) { + checkNonnegative(count, "count"); + + Count existingCounter; + int oldCount; + if (count == 0) { + existingCounter = backingMap.remove(element); + oldCount = getAndSet(existingCounter, count); + } else { + existingCounter = backingMap.get(element); + oldCount = getAndSet(existingCounter, count); + + if (existingCounter == null) { + backingMap.put(element, new Count(count)); + } + } + + size += (count - oldCount); + return oldCount; + } + + private static int getAndSet(Count i, int count) { + if (i == null) { + return 0; + } + + return i.getAndSet(count); + } + + // Views + + @Override Set createElementSet() { + return new MapBasedElementSet(); + } + + class MapBasedElementSet extends Multisets.ElementSet { + @Override + Multiset multiset() { + return AbstractMapBasedMultiset.this; + } + } + + // Don't allow default serialization. + @GwtIncompatible("java.io.ObjectStreamException") + @SuppressWarnings("unused") // actually used during deserialization + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + + @GwtIncompatible("not needed in emulated source.") + private static final long serialVersionUID = -2250766705698539974L; +} diff --git a/guava/src/com/google/common/collect/AbstractMapEntry.java b/guava/src/com/google/common/collect/AbstractMapEntry.java new file mode 100644 index 0000000..d790748 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractMapEntry.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} + * methods of {@code Entry}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractMapEntry implements Entry { + + @Override + public abstract K getKey(); + + @Override + public abstract V getValue(); + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + @Override public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form {@code {key}={value}}. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/guava/src/com/google/common/collect/AbstractMultimap.java b/guava/src/com/google/common/collect/AbstractMultimap.java new file mode 100644 index 0000000..e3b07a1 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractMultimap.java @@ -0,0 +1,1414 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link Multimap} interface. This class represents + * a multimap as a map that associates each key with a collection of values. All + * methods of {@link Multimap} are supported, including those specified as + * optional in the interface. + * + *

To implement a multimap, a subclass must define the method {@link + * #createCollection()}, which creates an empty collection of values for a key. + * + *

The multimap constructor takes a map that has a single entry for each + * distinct key. When you insert a key-value pair with a key that isn't already + * in the multimap, {@code AbstractMultimap} calls {@link #createCollection()} + * to create the collection of values for that key. The subclass should not call + * {@link #createCollection()} directly, and a new instance should be created + * every time the method is called. + * + *

For example, the subclass could pass a {@link java.util.TreeMap} during + * construction, and {@link #createCollection()} could return a {@link + * java.util.TreeSet}, in which case the multimap's iterators would propagate + * through the keys and values in sorted order. + * + *

Keys and values may be null, as long as the underlying collection classes + * support null elements. + * + *

The collections created by {@link #createCollection()} may or may not + * allow duplicates. If the collection, such as a {@link Set}, does not support + * duplicates, an added key-value pair will replace an existing pair with the + * same key and value, if such a pair is present. With collections like {@link + * List} that allow duplicates, the collection will keep the existing key-value + * pairs while adding a new pair. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap, even if the underlying map and {@link #createCollection()} method + * return threadsafe classes. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedMultimap}. + * + *

For serialization to work, the subclass must specify explicit + * {@code readObject} and {@code writeObject} methods. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractMultimap implements Multimap, Serializable { + /* + * Here's an outline of the overall design. + * + * The map variable contains the collection of values associated with each + * key. When a key-value pair is added to a multimap that didn't previously + * contain any values for that key, a new collection generated by + * createCollection is added to the map. That same collection instance + * remains in the map as long as the multimap has any values for the key. If + * all values for the key are removed, the key and collection are removed + * from the map. + * + * The get method returns a WrappedCollection, which decorates the collection + * in the map (if the key is present) or an empty collection (if the key is + * not present). When the collection delegate in the WrappedCollection is + * empty, the multimap may contain subsequently added values for that key. To + * handle that situation, the WrappedCollection checks whether map contains + * an entry for the provided key, and if so replaces the delegate. + */ + + private transient Map> map; + private transient int totalSize; + + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @throws IllegalArgumentException if {@code map} is not empty + */ + protected AbstractMultimap(Map> map) { + checkArgument(map.isEmpty()); + this.map = map; + } + + /** Used during deserialization only. */ + final void setMap(Map> map) { + this.map = map; + totalSize = 0; + for (Collection values : map.values()) { + checkArgument(!values.isEmpty()); + totalSize += values.size(); + } + } + + /** + * Creates the collection of values for a single key. + * + *

Collections with weak, soft, or phantom references are not supported. + * Each call to {@code createCollection} should create a new instance. + * + *

The returned collection class determines whether duplicate key-value + * pairs are allowed. + * + * @return an empty collection of values + */ + abstract Collection createCollection(); + + /** + * Creates the collection of values for an explicitly provided key. By + * default, it simply calls {@link #createCollection()}, which is the correct + * behavior for most implementations. The {@link LinkedHashMultimap} class + * overrides it. + * + * @param key key to associate with values in the collection + * @return an empty collection of values + */ + Collection createCollection(@Nullable K key) { + return createCollection(); + } + + Map> backingMap() { + return map; + } + + // Query Operations + + @Override + public int size() { + return totalSize; + } + + @Override + public boolean isEmpty() { + return totalSize == 0; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(@Nullable Object value) { + for (Collection collection : map.values()) { + if (collection.contains(value)) { + return true; + } + } + + return false; + } + + @Override + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + Collection collection = map.get(key); + return collection != null && collection.contains(value); + } + + // Modification Operations + + @Override + public boolean put(@Nullable K key, @Nullable V value) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + if (collection.add(value)) { + totalSize++; + map.put(key, collection); + return true; + } else { + throw new AssertionError("New Collection violated the Collection spec"); + } + } else if (collection.add(value)) { + totalSize++; + return true; + } else { + return false; + } + } + + private Collection getOrCreateCollection(@Nullable K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + map.put(key, collection); + } + return collection; + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + Collection collection = map.get(key); + if (collection == null) { + return false; + } + + boolean changed = collection.remove(value); + if (changed) { + totalSize--; + if (collection.isEmpty()) { + map.remove(key); + } + } + return changed; + } + + // Bulk Operations + + @Override + public boolean putAll(@Nullable K key, Iterable values) { + if (!values.iterator().hasNext()) { + return false; + } + // TODO(user): investigate atomic failure? + Collection collection = getOrCreateCollection(key); + int oldSize = collection.size(); + + boolean changed = false; + if (values instanceof Collection) { + Collection c = Collections2.cast(values); + changed = collection.addAll(c); + } else { + for (V value : values) { + changed |= collection.add(value); + } + } + + totalSize += (collection.size() - oldSize); + return changed; + } + + @Override + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Map.Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection replaceValues( + @Nullable K key, Iterable values) { + Iterator iterator = values.iterator(); + if (!iterator.hasNext()) { + return removeAll(key); + } + + // TODO(user): investigate atomic failure? + Collection collection = getOrCreateCollection(key); + Collection oldValues = createCollection(); + oldValues.addAll(collection); + + totalSize -= collection.size(); + collection.clear(); + + while (iterator.hasNext()) { + if (collection.add(iterator.next())) { + totalSize++; + } + } + + return unmodifiableCollectionSubclass(oldValues); + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection removeAll(@Nullable Object key) { + Collection collection = map.remove(key); + Collection output = createCollection(); + + if (collection != null) { + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + } + + return unmodifiableCollectionSubclass(output); + } + + private Collection unmodifiableCollectionSubclass( + Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } else { + return Collections.unmodifiableCollection(collection); + } + } + + @Override + public void clear() { + // Clear each collection, to make previously returned collections empty. + for (Collection collection : map.values()) { + collection.clear(); + } + map.clear(); + totalSize = 0; + } + + // Views + + /** + * {@inheritDoc} + * + *

The returned collection is not serializable. + */ + @Override + public Collection get(@Nullable K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + } + return wrapCollection(key, collection); + } + + /** + * Generates a decorated collection that remains consistent with the values in + * the multimap for the provided key. Changes to the multimap may alter the + * returned collection, and vice versa. + */ + private Collection wrapCollection( + @Nullable K key, Collection collection) { + if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else if (collection instanceof Set) { + return new WrappedSet(key, (Set) collection); + } else if (collection instanceof List) { + return wrapList(key, (List) collection, null); + } else { + return new WrappedCollection(key, collection, null); + } + } + + private List wrapList( + @Nullable K key, List list, @Nullable WrappedCollection ancestor) { + return (list instanceof RandomAccess) + ? new RandomAccessWrappedList(key, list, ancestor) + : new WrappedList(key, list, ancestor); + } + + /** + * Collection decorator that stays in sync with the multimap values for a key. + * There are two kinds of wrapped collections: full and subcollections. Both + * have a delegate pointing to the underlying collection class. + * + *

Full collections, identified by a null ancestor field, contain all + * multimap values for a given key. Its delegate is a value in {@link + * AbstractMultimap#map} whenever the delegate is non-empty. The {@code + * refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} methods ensure + * that the {@code WrappedCollection} and map remain consistent. + * + *

A subcollection, such as a sublist, contains some of the values for a + * given key. Its ancestor field points to the full wrapped collection with + * all values for the key. The subcollection {@code refreshIfEmpty}, {@code + * removeIfEmpty}, and {@code addToMap} methods call the corresponding methods + * of the full wrapped collection. + */ + private class WrappedCollection extends AbstractCollection { + final K key; + Collection delegate; + final WrappedCollection ancestor; + final Collection ancestorDelegate; + + WrappedCollection(@Nullable K key, Collection delegate, + @Nullable WrappedCollection ancestor) { + this.key = key; + this.delegate = delegate; + this.ancestor = ancestor; + this.ancestorDelegate + = (ancestor == null) ? null : ancestor.getDelegate(); + } + + /** + * If the delegate collection is empty, but the multimap has values for the + * key, replace the delegate with the new collection for the key. + * + *

For a subcollection, refresh its ancestor and validate that the + * ancestor delegate hasn't changed. + */ + void refreshIfEmpty() { + if (ancestor != null) { + ancestor.refreshIfEmpty(); + if (ancestor.getDelegate() != ancestorDelegate) { + throw new ConcurrentModificationException(); + } + } else if (delegate.isEmpty()) { + Collection newDelegate = map.get(key); + if (newDelegate != null) { + delegate = newDelegate; + } + } + } + + /** + * If collection is empty, remove it from {@code AbstractMultimap.this.map}. + * For subcollections, check whether the ancestor collection is empty. + */ + void removeIfEmpty() { + if (ancestor != null) { + ancestor.removeIfEmpty(); + } else if (delegate.isEmpty()) { + map.remove(key); + } + } + + K getKey() { + return key; + } + + /** + * Add the delegate to the map. Other {@code WrappedCollection} methods + * should call this method after adding elements to a previously empty + * collection. + * + *

Subcollection add the ancestor's delegate instead. + */ + void addToMap() { + if (ancestor != null) { + ancestor.addToMap(); + } else { + map.put(key, delegate); + } + } + + @Override public int size() { + refreshIfEmpty(); + return delegate.size(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + refreshIfEmpty(); + return delegate.equals(object); + } + + @Override public int hashCode() { + refreshIfEmpty(); + return delegate.hashCode(); + } + + @Override public String toString() { + refreshIfEmpty(); + return delegate.toString(); + } + + Collection getDelegate() { + return delegate; + } + + @Override public Iterator iterator() { + refreshIfEmpty(); + return new WrappedIterator(); + } + + /** Collection iterator for {@code WrappedCollection}. */ + class WrappedIterator implements Iterator { + final Iterator delegateIterator; + final Collection originalDelegate = delegate; + + WrappedIterator() { + delegateIterator = iteratorOrListIterator(delegate); + } + + WrappedIterator(Iterator delegateIterator) { + this.delegateIterator = delegateIterator; + } + + /** + * If the delegate changed since the iterator was created, the iterator is + * no longer valid. + */ + void validateIterator() { + refreshIfEmpty(); + if (delegate != originalDelegate) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + validateIterator(); + return delegateIterator.hasNext(); + } + + @Override + public V next() { + validateIterator(); + return delegateIterator.next(); + } + + @Override + public void remove() { + delegateIterator.remove(); + totalSize--; + removeIfEmpty(); + } + + Iterator getDelegateIterator() { + validateIterator(); + return delegateIterator; + } + } + + @Override public boolean add(V value) { + refreshIfEmpty(); + boolean wasEmpty = delegate.isEmpty(); + boolean changed = delegate.add(value); + if (changed) { + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + return changed; + } + + WrappedCollection getAncestor() { + return ancestor; + } + + // The following methods are provided for better performance. + + @Override public boolean addAll(Collection collection) { + if (collection.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.addAll(collection); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override public boolean contains(Object o) { + refreshIfEmpty(); + return delegate.contains(o); + } + + @Override public boolean containsAll(Collection c) { + refreshIfEmpty(); + return delegate.containsAll(c); + } + + @Override public void clear() { + int oldSize = size(); // calls refreshIfEmpty + if (oldSize == 0) { + return; + } + delegate.clear(); + totalSize -= oldSize; + removeIfEmpty(); // maybe shouldn't be removed if this is a sublist + } + + @Override public boolean remove(Object o) { + refreshIfEmpty(); + boolean changed = delegate.remove(o); + if (changed) { + totalSize--; + removeIfEmpty(); + } + return changed; + } + + @Override public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.removeAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + checkNotNull(c); + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.retainAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + private Iterator iteratorOrListIterator(Collection collection) { + return (collection instanceof List) + ? ((List) collection).listIterator() + : collection.iterator(); + } + + /** Set decorator that stays in sync with the multimap values for a key. */ + private class WrappedSet extends WrappedCollection implements Set { + WrappedSet(@Nullable K key, Set delegate) { + super(key, delegate, null); + } + + @Override + public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + + // Guava issue 1013: AbstractSet and most JDK set implementations are + // susceptible to quadratic removeAll performance on lists; + // use a slightly smarter implementation here + boolean changed = Sets.removeAllImpl((Set) delegate, c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + /** + * SortedSet decorator that stays in sync with the multimap values for a key. + */ + private class WrappedSortedSet extends WrappedCollection + implements SortedSet { + WrappedSortedSet(@Nullable K key, SortedSet delegate, + @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + SortedSet getSortedSetDelegate() { + return (SortedSet) getDelegate(); + } + + @Override + public Comparator comparator() { + return getSortedSetDelegate().comparator(); + } + + @Override + public V first() { + refreshIfEmpty(); + return getSortedSetDelegate().first(); + } + + @Override + public V last() { + refreshIfEmpty(); + return getSortedSetDelegate().last(); + } + + @Override + public SortedSet headSet(V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().headSet(toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet subSet(V fromElement, V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().subSet(fromElement, toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet tailSet(V fromElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().tailSet(fromElement), + (getAncestor() == null) ? this : getAncestor()); + } + } + + /** List decorator that stays in sync with the multimap values for a key. */ + private class WrappedList extends WrappedCollection implements List { + WrappedList(@Nullable K key, List delegate, + @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + List getListDelegate() { + return (List) getDelegate(); + } + + @Override + public boolean addAll(int index, Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = getListDelegate().addAll(index, c); + if (changed) { + int newSize = getDelegate().size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override + public V get(int index) { + refreshIfEmpty(); + return getListDelegate().get(index); + } + + @Override + public V set(int index, V element) { + refreshIfEmpty(); + return getListDelegate().set(index, element); + } + + @Override + public void add(int index, V element) { + refreshIfEmpty(); + boolean wasEmpty = getDelegate().isEmpty(); + getListDelegate().add(index, element); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + + @Override + public V remove(int index) { + refreshIfEmpty(); + V value = getListDelegate().remove(index); + totalSize--; + removeIfEmpty(); + return value; + } + + @Override + public int indexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + refreshIfEmpty(); + return new WrappedListIterator(); + } + + @Override + public ListIterator listIterator(int index) { + refreshIfEmpty(); + return new WrappedListIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + refreshIfEmpty(); + return wrapList(getKey(), + getListDelegate().subList(fromIndex, toIndex), + (getAncestor() == null) ? this : getAncestor()); + } + + /** ListIterator decorator. */ + private class WrappedListIterator extends WrappedIterator + implements ListIterator { + WrappedListIterator() {} + + public WrappedListIterator(int index) { + super(getListDelegate().listIterator(index)); + } + + private ListIterator getDelegateListIterator() { + return (ListIterator) getDelegateIterator(); + } + + @Override + public boolean hasPrevious() { + return getDelegateListIterator().hasPrevious(); + } + + @Override + public V previous() { + return getDelegateListIterator().previous(); + } + + @Override + public int nextIndex() { + return getDelegateListIterator().nextIndex(); + } + + @Override + public int previousIndex() { + return getDelegateListIterator().previousIndex(); + } + + @Override + public void set(V value) { + getDelegateListIterator().set(value); + } + + @Override + public void add(V value) { + boolean wasEmpty = isEmpty(); + getDelegateListIterator().add(value); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + } + } + + /** + * List decorator that stays in sync with the multimap values for a key and + * supports rapid random access. + */ + private class RandomAccessWrappedList extends WrappedList + implements RandomAccess { + RandomAccessWrappedList(@Nullable K key, List delegate, + @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + private Set createKeySet() { + return (map instanceof SortedMap) + ? new SortedKeySet((SortedMap>) map) : new KeySet(map); + } + + private class KeySet extends Maps.KeySet> { + + /** + * This is usually the same as map, except when someone requests a + * subcollection of a {@link SortedKeySet}. + */ + final Map> subMap; + + KeySet(final Map> subMap) { + this.subMap = subMap; + } + + @Override + Map> map() { + return subMap; + } + + @Override public Iterator iterator() { + return new Iterator() { + final Iterator>> entryIterator + = subMap.entrySet().iterator(); + Map.Entry> entry; + + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + @Override + public K next() { + entry = entryIterator.next(); + return entry.getKey(); + } + @Override + public void remove() { + Iterators.checkRemove(entry != null); + Collection collection = entry.getValue(); + entryIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + }; + } + + // The following methods are included for better performance. + + @Override public boolean remove(Object key) { + int count = 0; + Collection collection = subMap.remove(key); + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count > 0; + } + + @Override + public void clear() { + Iterators.clear(iterator()); + } + + @Override public boolean containsAll(Collection c) { + return subMap.keySet().containsAll(c); + } + + @Override public boolean equals(@Nullable Object object) { + return this == object || this.subMap.keySet().equals(object); + } + + @Override public int hashCode() { + return subMap.keySet().hashCode(); + } + } + + private class SortedKeySet extends KeySet implements SortedSet { + + SortedKeySet(SortedMap> subMap) { + super(subMap); + } + + SortedMap> sortedMap() { + return (SortedMap>) subMap; + } + + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public K first() { + return sortedMap().firstKey(); + } + + @Override + public SortedSet headSet(K toElement) { + return new SortedKeySet(sortedMap().headMap(toElement)); + } + + @Override + public K last() { + return sortedMap().lastKey(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); + } + + @Override + public SortedSet tailSet(K fromElement) { + return new SortedKeySet(sortedMap().tailMap(fromElement)); + } + } + + private transient Multiset multiset; + + @Override + public Multiset keys() { + Multiset result = multiset; + if (result == null) { + return multiset = new Multimaps.Keys() { + @Override Multimap multimap() { + return AbstractMultimap.this; + } + }; + } + return result; + } + + /** + * Removes all values for the provided key. Unlike {@link #removeAll}, it + * returns the number of removed mappings. + */ + private int removeValuesForKey(Object key) { + Collection collection; + try { + collection = map.remove(key); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + + int count = 0; + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count; + } + + private transient Collection valuesCollection; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * for one key, followed by the values of a second key, and so on. + */ + @Override public Collection values() { + Collection result = valuesCollection; + if (result == null) { + return valuesCollection = new Multimaps.Values() { + @Override Multimap multimap() { + return AbstractMultimap.this; + } + }; + } + return result; + } + + private transient Collection> entries; + + /* + * TODO(kevinb): should we copy this javadoc to each concrete class, so that + * classes like LinkedHashMultimap that need to say something different are + * still able to {@inheritDoc} all the way from Multimap? + */ + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * for one key, followed by the values of a second key, and so on. + * + *

Each entry is an immutable snapshot of a key-value mapping in the + * multimap, taken at the time the entry is returned by a method call to the + * collection or its iterator. + */ + @Override + public Collection> entries() { + Collection> result = entries; + return (result == null) ? entries = createEntries() : result; + } + + Collection> createEntries() { + if (this instanceof SetMultimap) { + return new Multimaps.EntrySet() { + @Override Multimap multimap() { + return AbstractMultimap.this; + } + + @Override public Iterator> iterator() { + return createEntryIterator(); + } + }; + } + return new Multimaps.Entries() { + @Override Multimap multimap() { + return AbstractMultimap.this; + } + + @Override public Iterator> iterator() { + return createEntryIterator(); + } + }; + } + + /** + * Returns an iterator across all key-value map entries, used by {@code + * entries().iterator()} and {@code values().iterator()}. The default + * behavior, which traverses the values for one key, the values for a second + * key, and so on, suffices for most {@code AbstractMultimap} implementations. + * + * @return an iterator across map entries + */ + Iterator> createEntryIterator() { + return new EntryIterator(); + } + + /** Iterator across all key-value pairs. */ + private class EntryIterator implements Iterator> { + final Iterator>> keyIterator; + K key; + Collection collection; + Iterator valueIterator; + + EntryIterator() { + keyIterator = map.entrySet().iterator(); + if (keyIterator.hasNext()) { + findValueIteratorAndKey(); + } else { + valueIterator = Iterators.emptyModifiableIterator(); + } + } + + void findValueIteratorAndKey() { + Map.Entry> entry = keyIterator.next(); + key = entry.getKey(); + collection = entry.getValue(); + valueIterator = collection.iterator(); + } + + @Override + public boolean hasNext() { + return keyIterator.hasNext() || valueIterator.hasNext(); + } + + @Override + public Map.Entry next() { + if (!valueIterator.hasNext()) { + findValueIteratorAndKey(); + } + return Maps.immutableEntry(key, valueIterator.next()); + } + + @Override + public void remove() { + valueIterator.remove(); + if (collection.isEmpty()) { + keyIterator.remove(); + } + totalSize--; + } + } + + private transient Map> asMap; + + @Override + public Map> asMap() { + Map> result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + private Map> createAsMap() { + return (map instanceof SortedMap) + ? new SortedAsMap((SortedMap>) map) : new AsMap(map); + } + + private class AsMap extends AbstractMap> { + /** + * Usually the same as map, but smaller for the headMap(), tailMap(), or + * subMap() of a SortedAsMap. + */ + final transient Map> submap; + + AsMap(Map> submap) { + this.submap = submap; + } + + transient Set>> entrySet; + + @Override public Set>> entrySet() { + Set>> result = entrySet; + return (result == null) ? entrySet = new AsMapEntries() : result; + } + + // The following methods are included for performance. + + @Override public boolean containsKey(Object key) { + return Maps.safeContainsKey(submap, key); + } + + @Override public Collection get(Object key) { + Collection collection = Maps.safeGet(submap, key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrapCollection(k, collection); + } + + @Override public Set keySet() { + return AbstractMultimap.this.keySet(); + } + + @Override + public int size() { + return submap.size(); + } + + @Override public Collection remove(Object key) { + Collection collection = submap.remove(key); + if (collection == null) { + return null; + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + return output; + } + + @Override public boolean equals(@Nullable Object object) { + return this == object || submap.equals(object); + } + + @Override public int hashCode() { + return submap.hashCode(); + } + + @Override public String toString() { + return submap.toString(); + } + + @Override + public void clear() { + if (submap == map) { + AbstractMultimap.this.clear(); + } else { + + Iterators.clear(new AsMapIterator()); + } + } + + class AsMapEntries extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override public Iterator>> iterator() { + return new AsMapIterator(); + } + + // The following methods are included for performance. + + @Override public boolean contains(Object o) { + return Collections2.safeContains(submap.entrySet(), o); + } + + @Override public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + /** Iterator across all keys and value collections. */ + class AsMapIterator implements Iterator>> { + final Iterator>> delegateIterator + = submap.entrySet().iterator(); + Collection collection; + + @Override + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + @Override + public Map.Entry> next() { + Map.Entry> entry = delegateIterator.next(); + K key = entry.getKey(); + collection = entry.getValue(); + return Maps.immutableEntry(key, wrapCollection(key, collection)); + } + + @Override + public void remove() { + delegateIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + } + } + + private class SortedAsMap extends AsMap + implements SortedMap> { + SortedAsMap(SortedMap> submap) { + super(submap); + } + + SortedMap> sortedMap() { + return (SortedMap>) submap; + } + + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public K firstKey() { + return sortedMap().firstKey(); + } + + @Override + public K lastKey() { + return sortedMap().lastKey(); + } + + @Override + public SortedMap> headMap(K toKey) { + return new SortedAsMap(sortedMap().headMap(toKey)); + } + + @Override + public SortedMap> subMap(K fromKey, K toKey) { + return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); + } + + @Override + public SortedMap> tailMap(K fromKey) { + return new SortedAsMap(sortedMap().tailMap(fromKey)); + } + + SortedSet sortedKeySet; + + // returns a SortedSet, even though returning a Set would be sufficient to + // satisfy the SortedMap.keySet() interface + @Override public SortedSet keySet() { + SortedSet result = sortedKeySet; + return (result == null) + ? sortedKeySet = new SortedKeySet(sortedMap()) : result; + } + } + + // Comparison and hashing + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.map.equals(that.asMap()); + } + return false; + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + * + * @see Map#hashCode + */ + @Override public int hashCode() { + return map.hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling + * {@code toString} on the map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override + public String toString() { + return map.toString(); + } + + private static final long serialVersionUID = 2447537837011683357L; +} diff --git a/guava/src/com/google/common/collect/AbstractMultiset.java b/guava/src/com/google/common/collect/AbstractMultiset.java new file mode 100644 index 0000000..20ea93f --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractMultiset.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Multisets.setCountImpl; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * This class provides a skeletal implementation of the {@link Multiset} + * interface. A new multiset implementation can be created easily by extending + * this class and implementing the {@link Multiset#entrySet()} method, plus + * optionally overriding {@link #add(Object, int)} and + * {@link #remove(Object, int)} to enable modifications to the multiset. + * + *

The {@link #count} and {@link #size} implementations all iterate across + * the set returned by {@link Multiset#entrySet()}, as do many methods acting on + * the set returned by {@link #elementSet()}. Override those methods for better + * performance. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractMultiset extends AbstractCollection + implements Multiset { + // Query Operations + + @Override public int size() { + return Multisets.sizeImpl(this); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public boolean contains(@Nullable Object element) { + return count(element) > 0; + } + + @Override public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @Override + public int count(@Nullable Object element) { + for (Entry entry : entrySet()) { + if (Objects.equal(entry.getElement(), element)) { + return entry.getCount(); + } + } + return 0; + } + + // Modification Operations + + @Override public boolean add(@Nullable E element) { + add(element, 1); + return true; + } + + @Override + public int add(@Nullable E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(@Nullable Object element) { + return remove(element, 1) > 0; + } + + @Override + public int remove(@Nullable Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override + public int setCount(@Nullable E element, int count) { + return setCountImpl(this, element, count); + } + + @Override + public boolean setCount(@Nullable E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

This implementation is highly efficient when {@code elementsToAdd} + * is itself a {@link Multiset}. + */ + @Override public boolean addAll(Collection elementsToAdd) { + return Multisets.addAllImpl(this, elementsToAdd); + } + + @Override public boolean removeAll(Collection elementsToRemove) { + return Multisets.removeAllImpl(this, elementsToRemove); + } + + @Override public boolean retainAll(Collection elementsToRetain) { + return Multisets.retainAllImpl(this, elementsToRetain); + } + + @Override public void clear() { + Iterators.clear(entryIterator()); + } + + // Views + + private transient Set elementSet; + + @Override + public Set elementSet() { + Set result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be + * returned by {@link #elementSet()}. + */ + Set createElementSet() { + return new ElementSet(); + } + + class ElementSet extends Multisets.ElementSet { + @Override + Multiset multiset() { + return AbstractMultiset.this; + } + } + + abstract Iterator> entryIterator(); + + abstract int distinctElements(); + + private transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + class EntrySet extends Multisets.EntrySet { + @Override Multiset multiset() { + return AbstractMultiset.this; + } + + @Override public Iterator> iterator() { + return entryIterator(); + } + + @Override public int size() { + return distinctElements(); + } + } + + Set> createEntrySet() { + return new EntrySet(); + } + + // Object methods + + /** + * {@inheritDoc} + * + *

This implementation returns {@code true} if {@code object} is a multiset + * of the same size and if, for each element, the two multisets have the same + * count. + */ + @Override public boolean equals(@Nullable Object object) { + return Multisets.equalsImpl(this, object); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the hash code of {@link + * Multiset#entrySet()}. + */ + @Override public int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the result of invoking {@code toString} on + * {@link Multiset#entrySet()}. + */ + @Override public String toString() { + return entrySet().toString(); + } +} diff --git a/guava/src/com/google/common/collect/AbstractSequentialIterator.java b/guava/src/com/google/common/collect/AbstractSequentialIterator.java new file mode 100644 index 0000000..c6567f5 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractSequentialIterator.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * This class provides a skeletal implementation of the {@code Iterator} + * interface for sequences whose next element can always be derived from the + * previous element. Null elements are not supported, nor is the + * {@link #remove()} method. + * + *

Example:

   {@code
+ *
+ *   Iterator powersOfTwo = 
+ *       new AbstractSequentialIterator(1) {
+ *         protected Integer computeNext(Integer previous) {
+ *           return (previous == 1 << 30) ? null : previous * 2;
+ *         }
+ *       };}
+ * + * @author Chris Povirk + * @since 12.0 (in Guava as {@code AbstractLinkedIterator} since 8.0) + */ +@GwtCompatible +public abstract class AbstractSequentialIterator + extends UnmodifiableIterator { + private T nextOrNull; + + /** + * Creates a new iterator with the given first element, or, if {@code + * firstOrNull} is null, creates a new empty iterator. + */ + protected AbstractSequentialIterator(@Nullable T firstOrNull) { + this.nextOrNull = firstOrNull; + } + + /** + * Returns the element that follows {@code previous}, or returns {@code null} + * if no elements remain. This method is invoked during each call to + * {@link #next()} in order to compute the result of a future call to + * {@code next()}. + */ + protected abstract T computeNext(T previous); + + @Override + public final boolean hasNext() { + return nextOrNull != null; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return nextOrNull; + } finally { + nextOrNull = computeNext(nextOrNull); + } + } +} diff --git a/guava/src/com/google/common/collect/AbstractSetMultimap.java b/guava/src/com/google/common/collect/AbstractSetMultimap.java new file mode 100644 index 0000000..fe68470 --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractSetMultimap.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link SetMultimap} interface. It's a wrapper + * around {@link AbstractMultimap} that converts the returned collections into + * {@code Sets}. The {@link #createCollection} method must return a {@code Set}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSetMultimap + extends AbstractMultimap implements SetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractSetMultimap(Map> map) { + super(map); + } + + @Override abstract Set createCollection(); + + // Following Javadoc copied from SetMultimap. + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link Collection} specified + * in the {@link Multimap} interface. + */ + @Override public Set get(@Nullable K key) { + return (Set) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link Collection} specified + * in the {@link Multimap} interface. + */ + @Override public Set> entries() { + return (Set>) super.entries(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link Collection} specified + * in the {@link Multimap} interface. + */ + @Override public Set removeAll(@Nullable Object key) { + return (Set) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link Collection} specified + * in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override public Set replaceValues( + @Nullable K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link Set} values. + */ + @Override public Map> asMap() { + return super.asMap(); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair + */ + @Override public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + */ + @Override public boolean equals(@Nullable Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 7431625294878419160L; +} diff --git a/guava/src/com/google/common/collect/AbstractSortedMultiset.java b/guava/src/com/google/common/collect/AbstractSortedMultiset.java new file mode 100644 index 0000000..d226d3d --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractSortedMultiset.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * This class provides a skeletal implementation of the {@link SortedMultiset} interface. + * + *

The {@link #count} and {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by + * {@link #elementSet()}. Override those methods for better performance. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractSortedMultiset extends AbstractMultiset implements SortedMultiset { + @GwtTransient final Comparator comparator; + + // needed for serialization + @SuppressWarnings("unchecked") + AbstractSortedMultiset() { + this((Comparator) Ordering.natural()); + } + + AbstractSortedMultiset(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + @Override + public SortedSet elementSet() { + return (SortedSet) super.elementSet(); + } + + @Override + SortedSet createElementSet() { + return new SortedMultisets.ElementSet() { + @Override + SortedMultiset multiset() { + return AbstractSortedMultiset.this; + } + }; + } + + @Override + public Comparator comparator() { + return comparator; + } + + @Override + public Entry firstEntry() { + Iterator> entryIterator = entryIterator(); + return entryIterator.hasNext() ? entryIterator.next() : null; + } + + @Override + public Entry lastEntry() { + Iterator> entryIterator = descendingEntryIterator(); + return entryIterator.hasNext() ? entryIterator.next() : null; + } + + @Override + public Entry pollFirstEntry() { + Iterator> entryIterator = entryIterator(); + if (entryIterator.hasNext()) { + Entry result = entryIterator.next(); + result = Multisets.immutableEntry(result.getElement(), result.getCount()); + entryIterator.remove(); + return result; + } + return null; + } + + @Override + public Entry pollLastEntry() { + Iterator> entryIterator = descendingEntryIterator(); + if (entryIterator.hasNext()) { + Entry result = entryIterator.next(); + result = Multisets.immutableEntry(result.getElement(), result.getCount()); + entryIterator.remove(); + return result; + } + return null; + } + + @Override + public SortedMultiset subMultiset(@Nullable E fromElement, BoundType fromBoundType, + @Nullable E toElement, BoundType toBoundType) { + // These are checked elsewhere, but NullPointerTester wants them checked eagerly. + checkNotNull(fromBoundType); + checkNotNull(toBoundType); + return tailMultiset(fromElement, fromBoundType).headMultiset(toElement, toBoundType); + } + + abstract Iterator> descendingEntryIterator(); + + Iterator descendingIterator() { + return Multisets.iteratorImpl(descendingMultiset()); + } + + private transient SortedMultiset descendingMultiset; + + @Override + public SortedMultiset descendingMultiset() { + SortedMultiset result = descendingMultiset; + return (result == null) ? descendingMultiset = createDescendingMultiset() : result; + } + + SortedMultiset createDescendingMultiset() { + return new SortedMultisets.DescendingMultiset() { + @Override + SortedMultiset forwardMultiset() { + return AbstractSortedMultiset.this; + } + + @Override + Iterator> entryIterator() { + return descendingEntryIterator(); + } + + @Override + public Iterator iterator() { + return descendingIterator(); + } + }; + } +} diff --git a/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java b/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java new file mode 100644 index 0000000..2be5f4b --- /dev/null +++ b/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link SortedSetMultimap} interface. It's a + * wrapper around {@link AbstractMultimap} that converts the returned + * collections into sorted sets. The {@link #createCollection} method + * must return a {@code SortedSet}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSortedSetMultimap + extends AbstractSetMultimap implements SortedSetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractSortedSetMultimap(Map> map) { + super(map); + } + + @Override abstract SortedSet createCollection(); + + // Following Javadoc copied from Multimap and SortedSetMultimap. + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + */ + @Override public SortedSet get(@Nullable K key) { + return (SortedSet) super.get(key); + } + + /** + * Removes all values associated with a given key. The returned collection is + * immutable. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + */ + @Override public SortedSet removeAll(@Nullable Object key) { + return (SortedSet) super.removeAll(key); + } + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. The returned collection is immutable. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link Collection} specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override public SortedSet replaceValues( + K key, Iterable values) { + return (SortedSet) super.replaceValues(key, values); + } + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, will + * update the underlying multimap. The map does not support {@code setValue} + * on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code + * asMap().get(Object)} has the same behavior as {@link #get}, returning a + * live collection. When passed a key that is not present, however, {@code + * asMap().get(Object)} returns {@code null} instead of an empty collection. + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link SortedSet} values. + */ + @Override public Map> asMap() { + return super.asMap(); + } + + /** + * {@inheritDoc} + * + * Consequently, the values do not follow their natural ordering or the + * ordering of the value comparator. + */ + @Override public Collection values() { + return super.values(); + } + + private static final long serialVersionUID = 430848587173315748L; +} diff --git a/guava/src/com/google/common/collect/AllEqualOrdering.java b/guava/src/com/google/common/collect/AllEqualOrdering.java new file mode 100644 index 0000000..c30164b --- /dev/null +++ b/guava/src/com/google/common/collect/AllEqualOrdering.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * An ordering that treats all references as equals, even nulls. + * + * @author Emily Soldal + */ +@GwtCompatible(serializable = true) +final class AllEqualOrdering extends Ordering implements Serializable { + static final AllEqualOrdering INSTANCE = new AllEqualOrdering(); + + @Override + public int compare(@Nullable Object left, @Nullable Object right) { + return 0; + } + + @Override + public List sortedCopy(Iterable iterable) { + return Lists.newArrayList(iterable); + } + + @Override + public ImmutableList immutableSortedCopy(Iterable iterable) { + return ImmutableList.copyOf(iterable); + } + + @SuppressWarnings("unchecked") + @Override + public Ordering reverse() { + return (Ordering) this; + } + + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.allEqual()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ArrayListMultimap.java b/guava/src/com/google/common/collect/ArrayListMultimap.java new file mode 100644 index 0000000..759c073 --- /dev/null +++ b/guava/src/com/google/common/collect/ArrayListMultimap.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@code Multimap} that uses an {@code ArrayList} to store + * the values for a given key. A {@link HashMap} associates each key with an + * {@link ArrayList} of values. + * + *

When iterating through the collections supplied by this class, the + * ordering of values for a given key agrees with the order in which the values + * were added. + * + *

This multimap allows duplicate key-value pairs. After adding a new + * key-value pair equal to an existing key-value pair, the {@code + * ArrayListMultimap} will contain entries for both the new value and the old + * value. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

The lists returned by {@link #get}, {@link #removeAll}, and {@link + * #replaceValues} all implement {@link java.util.RandomAccess}. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedListMultimap}. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public final class ArrayListMultimap extends AbstractListMultimap { + // Default from ArrayList + private static final int DEFAULT_VALUES_PER_KEY = 3; + + @VisibleForTesting transient int expectedValuesPerKey; + + /** + * Creates a new, empty {@code ArrayListMultimap} with the default initial + * capacities. + */ + public static ArrayListMultimap create() { + return new ArrayListMultimap(); + } + + /** + * Constructs an empty {@code ArrayListMultimap} with enough capacity to hold + * the specified numbers of keys and values without resizing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static ArrayListMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new ArrayListMultimap(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs an {@code ArrayListMultimap} with the same mappings as the + * specified multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static ArrayListMultimap create( + Multimap multimap) { + return new ArrayListMultimap(multimap); + } + + private ArrayListMultimap() { + super(new HashMap>()); + expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + } + + private ArrayListMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Maps.>newHashMapWithExpectedSize(expectedKeys)); + checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private ArrayListMultimap(Multimap multimap) { + this(multimap.keySet().size(), + (multimap instanceof ArrayListMultimap) ? + ((ArrayListMultimap) multimap).expectedValuesPerKey : + DEFAULT_VALUES_PER_KEY); + putAll(multimap); + } + + /** + * Creates a new, empty {@code ArrayList} to hold the collection of values for + * an arbitrary key. + */ + @Override List createCollection() { + return new ArrayList(expectedValuesPerKey); + } + + /** + * Reduces the memory used by this {@code ArrayListMultimap}, if feasible. + */ + public void trimToSize() { + for (Collection collection : backingMap().values()) { + ArrayList arrayList = (ArrayList) collection; + arrayList.trimToSize(); + } + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for + * each distinct key: the key, number of values for that key, and the + * key's values + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(expectedValuesPerKey); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible("java.io.ObjectOutputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = stream.readInt(); + int distinctKeys = Serialization.readCount(stream); + Map> map = Maps.newHashMapWithExpectedSize(distinctKeys); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + @GwtIncompatible("Not needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ArrayTable.java b/guava/src/com/google/common/collect/ArrayTable.java new file mode 100644 index 0000000..d13f00c --- /dev/null +++ b/guava/src/com/google/common/collect/ArrayTable.java @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Fixed-size {@link Table} implementation backed by a two-dimensional array. + * + *

The allowed row and column keys must be supplied when the table is + * created. The table always contains a mapping for every row key / column pair. + * The value corresponding to a given row and column is null unless another + * value is provided. + * + *

The table's size is constant: the product of the number of supplied row + * keys and the number of supplied column keys. The {@code remove} and {@code + * clear} methods are not supported by the table or its views. The {@link + * #erase} and {@link #eraseAll} methods may be used instead. + * + *

The ordering of the row and column keys provided when the table is + * constructed determines the iteration ordering across rows and columns in the + * table's views. None of the view iterators support {@link Iterator#remove}. + * If the table is modified after an iterator is created, the iterator remains + * valid. + * + *

This class requires less memory than the {@link HashBasedTable} and {@link + * TreeBasedTable} implementations, except when the table is sparse. + * + *

Null row keys or column keys are not permitted. + * + *

This class provides methods involving the underlying array structure, + * where the array indices correspond to the position of a row or column in the + * lists of allowed keys and values. See the {@link #at}, {@link #set}, {@link + * #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} methods for more + * details. + * + *

Note that this implementation is not synchronized. If multiple threads + * access the same cell of an {@code ArrayTable} concurrently and one of the + * threads modifies its value, there is no guarantee that the new value will be + * fully visible to the other threads. To guarantee that modifications are + * visible, synchronize access to the table. Unlike other {@code Table} + * implementations, synchronization is unnecessary between a thread that writes + * to one cell and a thread that reads from another. + * + *

See the Guava User Guide article on + * {@code Table}. + * + * @author Jared Levy + * @since 10.0 + */ +@Beta +public final class ArrayTable implements Table, Serializable { + + /** + * Creates an empty {@code ArrayTable}. + * + * @param rowKeys row keys that may be stored in the generated table + * @param columnKeys column keys that may be stored in the generated table + * @throws NullPointerException if any of the provided keys is null + * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys} + * contains duplicates or is empty + */ + public static ArrayTable create( + Iterable rowKeys, Iterable columnKeys) { + return new ArrayTable(rowKeys, columnKeys); + } + + /* + * TODO(jlevy): Add factory methods taking an Enum class, instead of an + * iterable, to specify the allowed row keys and/or column keys. Note that + * custom serialization logic is needed to support different enum sizes during + * serialization and deserialization. + */ + + /** + * Creates an {@code ArrayTable} with the mappings in the provided table. + * + *

If {@code table} includes a mapping with row key {@code r} and a + * separate mapping with column key {@code c}, the returned table contains a + * mapping with row key {@code r} and column key {@code c}. If that row key / + * column key pair in not in {@code table}, the pair maps to {@code null} in + * the generated table. + * + *

The returned table allows subsequent {@code put} calls with the row keys + * in {@code table.rowKeySet()} and the column keys in {@code + * table.columnKeySet()}. Calling {@link #put} with other keys leads to an + * {@code IllegalArgumentException}. + * + *

The ordering of {@code table.rowKeySet()} and {@code + * table.columnKeySet()} determines the row and column iteration ordering of + * the returned table. + * + * @throws NullPointerException if {@code table} has a null key + * @throws IllegalArgumentException if the provided table is empty + */ + public static ArrayTable create(Table table) { + return new ArrayTable(table); + } + + /** + * Creates an {@code ArrayTable} with the same mappings, allowed keys, and + * iteration ordering as the provided {@code ArrayTable}. + */ + public static ArrayTable create( + ArrayTable table) { + return new ArrayTable(table); + } + + private final ImmutableList rowList; + private final ImmutableList columnList; + + // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex? + private final ImmutableMap rowKeyToIndex; + private final ImmutableMap columnKeyToIndex; + private final V[][] array; + + private ArrayTable(Iterable rowKeys, + Iterable columnKeys) { + this.rowList = ImmutableList.copyOf(rowKeys); + this.columnList = ImmutableList.copyOf(columnKeys); + checkArgument(!rowList.isEmpty()); + checkArgument(!columnList.isEmpty()); + + /* + * TODO(jlevy): Support empty rowKeys or columnKeys? If we do, when + * columnKeys is empty but rowKeys isn't, the table is empty but + * containsRow() can return true and rowKeySet() isn't empty. + */ + rowKeyToIndex = index(rowList); + columnKeyToIndex = index(columnList); + + @SuppressWarnings("unchecked") + V[][] tmpArray + = (V[][]) new Object[rowList.size()][columnList.size()]; + array = tmpArray; + } + + private static ImmutableMap index(List list) { + ImmutableMap.Builder columnBuilder = ImmutableMap.builder(); + for (int i = 0; i < list.size(); i++) { + columnBuilder.put(list.get(i), i); + } + return columnBuilder.build(); + } + + private ArrayTable(Table table) { + this(table.rowKeySet(), table.columnKeySet()); + putAll(table); + } + + private ArrayTable(ArrayTable table) { + rowList = table.rowList; + columnList = table.columnList; + rowKeyToIndex = table.rowKeyToIndex; + columnKeyToIndex = table.columnKeyToIndex; + @SuppressWarnings("unchecked") + V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()]; + array = copy; + for (int i = 0; i < rowList.size(); i++) { + System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); + } + } + + private abstract static class ArrayMap extends Maps.ImprovedAbstractMap { + private final ImmutableMap keyIndex; + + private ArrayMap(ImmutableMap keyIndex) { + this.keyIndex = keyIndex; + } + + @Override + public Set keySet() { + return keyIndex.keySet(); + } + + K getKey(int index) { + return keyIndex.keySet().asList().get(index); + } + + abstract String getKeyRole(); + + @Nullable abstract V getValue(int index); + + @Nullable abstract V setValue(int index, V newValue); + + @Override + public int size() { + return keyIndex.size(); + } + + @Override + public boolean isEmpty() { + return keyIndex.isEmpty(); + } + + @Override + protected Set> createEntrySet() { + return new Maps.EntrySet() { + @Override + Map map() { + return ArrayMap.this; + } + + @Override + public Iterator> iterator() { + return new AbstractIndexedListIterator>(size()) { + @Override + protected Entry get(final int index) { + return new AbstractMapEntry() { + @Override + public K getKey() { + return ArrayMap.this.getKey(index); + } + + @Override + public V getValue() { + return ArrayMap.this.getValue(index); + } + + @Override + public V setValue(V value) { + return ArrayMap.this.setValue(index, value); + } + }; + } + }; + } + }; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return keyIndex.containsKey(key); + } + + @Override + public V get(@Nullable Object key) { + Integer index = keyIndex.get(key); + if (index == null) { + return null; + } else { + return getValue(index); + } + } + + @Override + public V put(K key, V value) { + Integer index = keyIndex.get(key); + if (index == null) { + throw new IllegalArgumentException( + getKeyRole() + " " + key + " not in " + keyIndex.keySet()); + } + return setValue(index, value); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns, as an immutable list, the row keys provided when the table was + * constructed, including those that are mapped to null values only. + */ + public ImmutableList rowKeyList() { + return rowList; + } + + /** + * Returns, as an immutable list, the column keys provided when the table was + * constructed, including those that are mapped to null values only. + */ + public ImmutableList columnKeyList() { + return columnList; + } + + /** + * Returns the value corresponding to the specified row and column indices. + * The same value is returned by {@code + * get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but + * this method runs more quickly. + * + * @param rowIndex position of the row key in {@link #rowKeyList()} + * @param columnIndex position of the row key in {@link #columnKeyList()} + * @return the value with the specified row and column + * @throws IndexOutOfBoundsException if either index is negative, {@code + * rowIndex} is greater then or equal to the number of allowed row keys, + * or {@code columnIndex} is greater then or equal to the number of + * allowed column keys + */ + public V at(int rowIndex, int columnIndex) { + return array[rowIndex][columnIndex]; + } + + /** + * Associates {@code value} with the specified row and column indices. The + * logic {@code + * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)} + * has the same behavior, but this method runs more quickly. + * + * @param rowIndex position of the row key in {@link #rowKeyList()} + * @param columnIndex position of the row key in {@link #columnKeyList()} + * @param value value to store in the table + * @return the previous value with the specified row and column + * @throws IndexOutOfBoundsException if either index is negative, {@code + * rowIndex} is greater then or equal to the number of allowed row keys, + * or {@code columnIndex} is greater then or equal to the number of + * allowed column keys + */ + public V set(int rowIndex, int columnIndex, @Nullable V value) { + V oldValue = array[rowIndex][columnIndex]; + array[rowIndex][columnIndex] = value; + return oldValue; + } + + /** + * Returns a two-dimensional array with the table contents. The row and column + * indices correspond to the positions of the row and column in the iterables + * provided during table construction. If the table lacks a mapping for a + * given row and column, the corresponding array element is null. + * + *

Subsequent table changes will not modify the array, and vice versa. + * + * @param valueClass class of values stored in the returned array + */ + public V[][] toArray(Class valueClass) { + // Can change to use varargs in JDK 1.6 if we want + @SuppressWarnings("unchecked") // TODO: safe? + V[][] copy = (V[][]) Array.newInstance( + valueClass, new int[] { rowList.size(), columnList.size() }); + for (int i = 0; i < rowList.size(); i++) { + System.arraycopy(array[i], 0, copy[i], 0, array[i].length); + } + return copy; + } + + /** + * Not supported. Use {@link #eraseAll} instead. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link #eraseAll} + */ + @Override + @Deprecated public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Associates the value {@code null} with every pair of allowed row and column + * keys. + */ + public void eraseAll() { + for (V[] row : array) { + Arrays.fill(row, null); + } + } + + /** + * Returns {@code true} if the provided keys are among the keys provided when + * the table was constructed. + */ + @Override + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { + return containsRow(rowKey) && containsColumn(columnKey); + } + + /** + * Returns {@code true} if the provided column key is among the column keys + * provided when the table was constructed. + */ + @Override + public boolean containsColumn(@Nullable Object columnKey) { + return columnKeyToIndex.containsKey(columnKey); + } + + /** + * Returns {@code true} if the provided row key is among the row keys + * provided when the table was constructed. + */ + @Override + public boolean containsRow(@Nullable Object rowKey) { + return rowKeyToIndex.containsKey(rowKey); + } + + @Override + public boolean containsValue(@Nullable Object value) { + for (V[] row : array) { + for (V element : row) { + if (Objects.equal(value, element)) { + return true; + } + } + } + return false; + } + + @Override + public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return (rowIndex == null || columnIndex == null) + ? null : array[rowIndex][columnIndex]; + } + + /** + * Always returns {@code false}. + */ + @Override + public boolean isEmpty() { + return false; + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if {@code rowKey} is not in {@link + * #rowKeySet()} or {@code columnKey} is not in {@link #columnKeySet()}. + */ + @Override + public V put(R rowKey, C columnKey, @Nullable V value) { + checkNotNull(rowKey); + checkNotNull(columnKey); + Integer rowIndex = rowKeyToIndex.get(rowKey); + checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList); + Integer columnIndex = columnKeyToIndex.get(columnKey); + checkArgument(columnIndex != null, + "Column %s not in %s", columnKey, columnList); + return set(rowIndex, columnIndex, value); + } + + /* + * TODO(jlevy): Consider creating a merge() method, similar to putAll() but + * copying non-null values only. + */ + + /** + * {@inheritDoc} + * + *

If {@code table} is an {@code ArrayTable}, its null values will be + * stored in this table, possibly replacing values that were previously + * non-null. + * + * @throws NullPointerException if {@code table} has a null key + * @throws IllegalArgumentException if any of the provided table's row keys or + * column keys is not in {@link #rowKeySet()} or {@link #columnKeySet()} + */ + @Override + public void putAll(Table table) { + for (Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + /** + * Not supported. Use {@link #erase} instead. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link #erase} + */ + @Override + @Deprecated public V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + /** + * Associates the value {@code null} with the specified keys, assuming both + * keys are valid. If either key is null or isn't among the keys provided + * during construction, this method has no effect. + * + *

This method is equivalent to {@code put(rowKey, columnKey, null)} when + * both provided keys are valid. + * + * @param rowKey row key of mapping to be erased + * @param columnKey column key of mapping to be erased + * @return the value previously associated with the keys, or {@code null} if + * no mapping existed for the keys + */ + public V erase(@Nullable Object rowKey, @Nullable Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + if (rowIndex == null || columnIndex == null) { + return null; + } + return set(rowIndex, columnIndex, null); + } + + // TODO(jlevy): Add eraseRow and eraseColumn methods? + + @Override + public int size() { + return rowList.size() * columnList.size(); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof Table) { + Table other = (Table) obj; + return cellSet().equals(other.cellSet()); + } + return false; + } + + @Override public int hashCode() { + return cellSet().hashCode(); + } + + /** + * Returns the string representation {@code rowMap().toString()}. + */ + @Override public String toString() { + return rowMap().toString(); + } + + private transient CellSet cellSet; + + /** + * Returns an unmodifiable set of all row key / column key / value + * triplets. Changes to the table will update the returned set. + * + *

The returned set's iterator traverses the mappings with the first row + * key, the mappings with the second row key, and so on. + * + *

The value in the returned cells may change if the table subsequently + * changes. + * + * @return set of table cells consisting of row key / column key / value + * triplets + */ + @Override + public Set> cellSet() { + CellSet set = cellSet; + return (set == null) ? cellSet = new CellSet() : set; + } + + private class CellSet extends AbstractSet> { + + @Override public Iterator> iterator() { + return new AbstractIndexedListIterator>(size()) { + @Override protected Cell get(final int index) { + return new Tables.AbstractCell() { + final int rowIndex = index / columnList.size(); + final int columnIndex = index % columnList.size(); + @Override + public R getRowKey() { + return rowList.get(rowIndex); + } + @Override + public C getColumnKey() { + return columnList.get(columnIndex); + } + @Override + public V getValue() { + return array[rowIndex][columnIndex]; + } + }; + } + }; + } + + @Override public int size() { + return ArrayTable.this.size(); + } + + @Override public boolean contains(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + Integer rowIndex = rowKeyToIndex.get(cell.getRowKey()); + Integer columnIndex = columnKeyToIndex.get(cell.getColumnKey()); + return rowIndex != null + && columnIndex != null + && Objects.equal(array[rowIndex][columnIndex], cell.getValue()); + } + return false; + } + } + + /** + * Returns a view of all mappings that have the given column key. If the + * column key isn't in {@link #columnKeySet()}, an empty immutable map is + * returned. + * + *

Otherwise, for each row key in {@link #rowKeySet()}, the returned map + * associates the row key with the corresponding value in the table. Changes + * to the returned map will update the underlying table, and vice versa. + * + * @param columnKey key of column to search for in the table + * @return the corresponding map from row keys to values + */ + @Override + public Map column(C columnKey) { + checkNotNull(columnKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return (columnIndex == null) + ? ImmutableMap.of() : new Column(columnIndex); + } + + private class Column extends ArrayMap { + final int columnIndex; + + Column(int columnIndex) { + super(rowKeyToIndex); + this.columnIndex = columnIndex; + } + + @Override + String getKeyRole() { + return "Row"; + } + + @Override + V getValue(int index) { + return at(index, columnIndex); + } + + @Override + V setValue(int index, V newValue) { + return set(index, columnIndex, newValue); + } + } + + /** + * Returns an immutable set of the valid column keys, including those that + * are associated with null values only. + * + * @return immutable set of column keys + */ + @Override + public ImmutableSet columnKeySet() { + return columnKeyToIndex.keySet(); + } + + private transient ColumnMap columnMap; + + @Override + public Map> columnMap() { + ColumnMap map = columnMap; + return (map == null) ? columnMap = new ColumnMap() : map; + } + + private class ColumnMap extends ArrayMap> { + private ColumnMap() { + super(columnKeyToIndex); + } + + @Override + String getKeyRole() { + return "Column"; + } + + @Override + Map getValue(int index) { + return new Column(index); + } + + @Override + Map setValue(int index, Map newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Map put(C key, Map value) { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns a view of all mappings that have the given row key. If the + * row key isn't in {@link #rowKeySet()}, an empty immutable map is + * returned. + * + *

Otherwise, for each column key in {@link #columnKeySet()}, the returned + * map associates the column key with the corresponding value in the + * table. Changes to the returned map will update the underlying table, and + * vice versa. + * + * @param rowKey key of row to search for in the table + * @return the corresponding map from column keys to values + */ + @Override + public Map row(R rowKey) { + checkNotNull(rowKey); + Integer rowIndex = rowKeyToIndex.get(rowKey); + return (rowIndex == null) ? ImmutableMap.of() : new Row(rowIndex); + } + + private class Row extends ArrayMap { + final int rowIndex; + + Row(int rowIndex) { + super(columnKeyToIndex); + this.rowIndex = rowIndex; + } + + @Override + String getKeyRole() { + return "Column"; + } + + @Override + V getValue(int index) { + return at(rowIndex, index); + } + + @Override + V setValue(int index, V newValue) { + return set(rowIndex, index, newValue); + } + } + + /** + * Returns an immutable set of the valid row keys, including those that are + * associated with null values only. + * + * @return immutable set of row keys + */ + @Override + public ImmutableSet rowKeySet() { + return rowKeyToIndex.keySet(); + } + + private transient RowMap rowMap; + + @Override + public Map> rowMap() { + RowMap map = rowMap; + return (map == null) ? rowMap = new RowMap() : map; + } + + private class RowMap extends ArrayMap> { + private RowMap() { + super(rowKeyToIndex); + } + + @Override + String getKeyRole() { + return "Row"; + } + + @Override + Map getValue(int index) { + return new Row(index); + } + + @Override + Map setValue(int index, Map newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Map put(R key, Map value) { + throw new UnsupportedOperationException(); + } + } + + private transient Collection values; + + /** + * Returns an unmodifiable collection of all values, which may contain + * duplicates. Changes to the table will update the returned collection. + * + *

The returned collection's iterator traverses the values of the first row + * key, the values of the second row key, and so on. + * + * @return collection of values + */ + @Override + public Collection values() { + Collection v = values; + return (v == null) ? values = new Values() : v; + } + + private class Values extends AbstractCollection { + @Override public Iterator iterator() { + return new TransformedIterator, V>(cellSet().iterator()) { + @Override + V transform(Cell cell) { + return cell.getValue(); + } + }; + } + + @Override public int size() { + return ArrayTable.this.size(); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/AsynchronousComputationException.java b/guava/src/com/google/common/collect/AsynchronousComputationException.java new file mode 100644 index 0000000..e64e17b --- /dev/null +++ b/guava/src/com/google/common/collect/AsynchronousComputationException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +/** + * Wraps an exception that occurred during a computation in a different thread. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + * @deprecated this class is unused by com.google.common.collect. This class + * is scheduled for deletion in November 2012. + */ +@Deprecated +public +class AsynchronousComputationException extends ComputationException { + /** + * Creates a new instance with the given cause. + */ + public AsynchronousComputationException(Throwable cause) { + super(cause); + } + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/BiMap.java b/guava/src/com/google/common/collect/BiMap.java new file mode 100644 index 0000000..a6203e0 --- /dev/null +++ b/guava/src/com/google/common/collect/BiMap.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of + * its values as well as that of its keys. This constraint enables bimaps to + * support an "inverse view", which is another bimap containing the same entries + * as this bimap but with reversed keys and values. + * + *

See the Guava User Guide article on + * {@code BiMap}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface BiMap extends Map { + // Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the given value is already bound to a + * different key in this bimap. The bimap will remain unmodified in this + * event. To avoid this exception, call {@link #forcePut} instead. + */ + @Override + V put(@Nullable K key, @Nullable V value); + + /** + * An alternate form of {@code put} that silently removes any existing entry + * with the value {@code value} before proceeding with the {@link #put} + * operation. If the bimap previously contained the provided key-value + * mapping, this method has no effect. + * + *

Note that a successful call to this method could cause the size of the + * bimap to increase by one, stay the same, or even decrease by one. + * + *

Warning: If an existing entry with this value is removed, the key + * for that entry is discarded and not returned. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the value which was previously associated with the key, which may + * be {@code null}, or {@code null} if there was no previous entry + */ + V forcePut(@Nullable K key, @Nullable V value); + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

Warning: the results of calling this method may vary depending on + * the iteration order of {@code map}. + * + * @throws IllegalArgumentException if an attempt to {@code put} any + * entry fails. Note that some map entries may have been added to the + * bimap before the exception was thrown. + */ + @Override + void putAll(Map map); + + // Views + + /** + * {@inheritDoc} + * + *

Because a bimap has unique values, this method returns a {@link Set}, + * instead of the {@link java.util.Collection} specified in the {@link Map} + * interface. + */ + @Override + Set values(); + + /** + * Returns the inverse view of this bimap, which maps each of this bimap's + * values to its associated key. The two bimaps are backed by the same data; + * any changes to one will appear in the other. + * + *

Note:There is no guaranteed correspondence between the iteration + * order of a bimap and that of its inverse. + * + * @return the inverse view of this bimap + */ + BiMap inverse(); +} diff --git a/guava/src/com/google/common/collect/BoundType.java b/guava/src/com/google/common/collect/BoundType.java new file mode 100644 index 0000000..2290048 --- /dev/null +++ b/guava/src/com/google/common/collect/BoundType.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Indicates whether an endpoint of some range is contained in the range itself ("closed") or not + * ("open"). If a range is unbounded on a side, it is neither open nor closed on that side; the + * bound simply does not exist. + * + * @since 10.0 + */ +@Beta +@GwtCompatible +public enum BoundType { + /** + * The endpoint value is not considered part of the set ("exclusive"). + */ + OPEN { + @Override + BoundType flip() { + return CLOSED; + } + }, + /** + * The endpoint value is considered part of the set ("inclusive"). + */ + CLOSED { + @Override + BoundType flip() { + return OPEN; + } + }; + + /** + * Returns the bound type corresponding to a boolean value for inclusivity. + */ + static BoundType forBoolean(boolean inclusive) { + return inclusive ? CLOSED : OPEN; + } + + abstract BoundType flip(); +} diff --git a/guava/src/com/google/common/collect/ByFunctionOrdering.java b/guava/src/com/google/common/collect/ByFunctionOrdering.java new file mode 100644 index 0000000..c148ba9 --- /dev/null +++ b/guava/src/com/google/common/collect/ByFunctionOrdering.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Objects; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * An ordering that orders elements by applying an order to the result of a + * function on those elements. + */ +@GwtCompatible(serializable = true) +final class ByFunctionOrdering + extends Ordering implements Serializable { + final Function function; + final Ordering ordering; + + ByFunctionOrdering( + Function function, Ordering ordering) { + this.function = checkNotNull(function); + this.ordering = checkNotNull(ordering); + } + + @Override public int compare(F left, F right) { + return ordering.compare(function.apply(left), function.apply(right)); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ByFunctionOrdering) { + ByFunctionOrdering that = (ByFunctionOrdering) object; + return this.function.equals(that.function) + && this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(function, ordering); + } + + @Override public String toString() { + return ordering + ".onResultOf(" + function + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ClassToInstanceMap.java b/guava/src/com/google/common/collect/ClassToInstanceMap.java new file mode 100644 index 0000000..b3f535c --- /dev/null +++ b/guava/src/com/google/common/collect/ClassToInstanceMap.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A map, each entry of which maps a Java + * raw type to an instance of that type. + * In addition to implementing {@code Map}, the additional type-safe operations + * {@link #putInstance} and {@link #getInstance} are available. + * + *

Like any other {@code Map}, this map may contain entries + * for primitive types, and a primitive type and its corresponding wrapper type + * may map to different values. + * + *

See the Guava User Guide article on + * {@code ClassToInstanceMap}. + * + *

To map a generic type to an instance of that type, use {@link + * com.google.common.reflect.TypeToInstanceMap} instead. + * + * @param the common supertype that all entries must share; often this is + * simply {@link Object} + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface ClassToInstanceMap extends Map, B> { + /** + * Returns the value the specified class is mapped to, or {@code null} if no + * entry for this class is present. This will only return a value that was + * bound to this specific class, not a value that may have been bound to a + * subtype. + */ + T getInstance(Class type); + + /** + * Maps the specified class to the specified value. Does not associate + * this value with any of the class's supertypes. + * + * @return the value previously associated with this class (possibly {@code + * null}), or {@code null} if there was no previous entry. + */ + T putInstance(Class type, @Nullable T value); +} diff --git a/guava/src/com/google/common/collect/Collections2.java b/guava/src/com/google/common/collect/Collections2.java new file mode 100644 index 0000000..e3ae14a --- /dev/null +++ b/guava/src/com/google/common/collect/Collections2.java @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.LongMath.binomial; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Provides static methods for working with {@code Collection} instances. + * + * @author Chris Povirk + * @author Mike Bostock + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Collections2 { + private Collections2() {} + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * returned collection is a live view of {@code unfiltered}; changes to one + * affect the other. + * + *

The resulting collection's iterator does not support {@code remove()}, + * but all other collection methods are supported. When given an element that + * doesn't satisfy the predicate, the collection's {@code add()} and {@code + * addAll()} methods throw an {@link IllegalArgumentException}. When methods + * such as {@code removeAll()} and {@code clear()} are called on the filtered + * collection, only elements that satisfy the filter will be removed from the + * underlying collection. + * + *

The returned collection isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered collection's methods, such as {@code size()}, + * iterate across every element in the underlying collection and determine + * which elements satisfy the filter. When a live view is not needed, + * it may be faster to copy {@code Iterables.filter(unfiltered, predicate)} + * and use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, + * as documented at {@link Predicate#apply}. Do not provide a predicate such + * as {@code Predicates.instanceOf(ArrayList.class)}, which is inconsistent + * with equals. (See {@link Iterables#filter(Iterable, Class)} for related + * functionality.) + */ + // TODO(kevinb): how can we omit that Iterables link when building gwt + // javadoc? + public static Collection filter( + Collection unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredCollection) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + return ((FilteredCollection) unfiltered).createCombined(predicate); + } + + return new FilteredCollection( + checkNotNull(unfiltered), checkNotNull(predicate)); + } + + /** + * Delegates to {@link Collection#contains}. Returns {@code false} if the + * {@code contains} method throws a {@code ClassCastException}. + */ + static boolean safeContains(Collection collection, Object object) { + try { + return collection.contains(object); + } catch (ClassCastException e) { + return false; + } + } + + static class FilteredCollection implements Collection { + final Collection unfiltered; + final Predicate predicate; + + FilteredCollection(Collection unfiltered, + Predicate predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + FilteredCollection createCombined(Predicate newPredicate) { + return new FilteredCollection(unfiltered, + Predicates.and(predicate, newPredicate)); + // . above needed to compile in JDK 5 + } + + @Override + public boolean add(E element) { + checkArgument(predicate.apply(element)); + return unfiltered.add(element); + } + + @Override + public boolean addAll(Collection collection) { + for (E element : collection) { + checkArgument(predicate.apply(element)); + } + return unfiltered.addAll(collection); + } + + @Override + public void clear() { + Iterables.removeIf(unfiltered, predicate); + } + + @Override + public boolean contains(Object element) { + try { + // unsafe cast can result in a CCE from predicate.apply(), which we + // will catch + @SuppressWarnings("unchecked") + E e = (E) element; + + /* + * We check whether e satisfies the predicate, when we really mean to + * check whether the element contained in the set does. This is ok as + * long as the predicate is consistent with equals, as required. + */ + return predicate.apply(e) && unfiltered.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!contains(element)) { + return false; + } + } + return true; + } + + @Override + public boolean isEmpty() { + return !Iterators.any(unfiltered.iterator(), predicate); + } + + @Override + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + + @Override + public boolean remove(Object element) { + try { + // unsafe cast can result in a CCE from predicate.apply(), which we + // will catch + @SuppressWarnings("unchecked") + E e = (E) element; + + // See comment in contains() concerning predicate.apply(e) + return predicate.apply(e) && unfiltered.remove(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean removeAll(final Collection collection) { + checkNotNull(collection); + Predicate combinedPredicate = new Predicate() { + @Override + public boolean apply(E input) { + return predicate.apply(input) && collection.contains(input); + } + }; + return Iterables.removeIf(unfiltered, combinedPredicate); + } + + @Override + public boolean retainAll(final Collection collection) { + checkNotNull(collection); + Predicate combinedPredicate = new Predicate() { + @Override + public boolean apply(E input) { + // See comment in contains() concerning predicate.apply(e) + return predicate.apply(input) && !collection.contains(input); + } + }; + return Iterables.removeIf(unfiltered, combinedPredicate); + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + + @Override + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override + public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + + @Override public String toString() { + return Iterators.toString(iterator()); + } + } + + /** + * Returns a collection that applies {@code function} to each element of + * {@code fromCollection}. The returned collection is a live view of {@code + * fromCollection}; changes to one affect the other. + * + *

The returned collection's {@code add()} and {@code addAll()} methods + * throw an {@link UnsupportedOperationException}. All other collection + * methods are supported, as long as {@code fromCollection} supports them. + * + *

The returned collection isn't threadsafe or serializable, even if + * {@code fromCollection} is. + * + *

When a live view is not needed, it may be faster to copy the + * transformed collection and use the copy. + * + *

If the input {@code Collection} is known to be a {@code List}, consider + * {@link Lists#transform}. If only an {@code Iterable} is available, use + * {@link Iterables#transform}. + */ + public static Collection transform(Collection fromCollection, + Function function) { + return new TransformedCollection(fromCollection, function); + } + + static class TransformedCollection extends AbstractCollection { + final Collection fromCollection; + final Function function; + + TransformedCollection(Collection fromCollection, + Function function) { + this.fromCollection = checkNotNull(fromCollection); + this.function = checkNotNull(function); + } + + @Override public void clear() { + fromCollection.clear(); + } + + @Override public boolean isEmpty() { + return fromCollection.isEmpty(); + } + + @Override public Iterator iterator() { + return Iterators.transform(fromCollection.iterator(), function); + } + + @Override public int size() { + return fromCollection.size(); + } + } + + /** + * Returns {@code true} if the collection {@code self} contains all of the + * elements in the collection {@code c}. + * + *

This method iterates over the specified collection {@code c}, checking + * each element returned by the iterator in turn to see if it is contained in + * the specified collection {@code self}. If all elements are so contained, + * {@code true} is returned, otherwise {@code false}. + * + * @param self a collection which might contain all elements in {@code c} + * @param c a collection whose elements might be contained by {@code self} + */ + static boolean containsAllImpl(Collection self, Collection c) { + checkNotNull(self); + for (Object o : c) { + if (!self.contains(o)) { + return false; + } + } + return true; + } + + /** + * An implementation of {@link Collection#toString()}. + */ + static String toStringImpl(final Collection collection) { + StringBuilder sb + = newStringBuilderForCollection(collection.size()).append('['); + STANDARD_JOINER.appendTo( + sb, Iterables.transform(collection, new Function() { + @Override public Object apply(Object input) { + return input == collection ? "(this Collection)" : input; + } + })); + return sb.append(']').toString(); + } + + /** + * Returns best-effort-sized StringBuilder based on the given collection size. + */ + static StringBuilder newStringBuilderForCollection(int size) { + checkArgument(size >= 0, "size must be non-negative"); + return new StringBuilder((int) Math.min(size * 8L, Ints.MAX_POWER_OF_TWO)); + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + static Collection cast(Iterable iterable) { + return (Collection) iterable; + } + + static final Joiner STANDARD_JOINER = Joiner.on(", ").useForNull("null"); + + /** + * Returns a {@link Collection} of all the permutations of the specified + * {@link Iterable}. + * + *

Notes: This is an implementation of the algorithm for + * Lexicographical Permutations Generation, described in Knuth's "The Art of + * Computer Programming", Volume 4, Chapter 7, Section 7.2.1.2. The + * iteration order follows the lexicographical order. This means that + * the first permutation will be in ascending order, and the last will be in + * descending order. + * + *

Duplicate elements are considered equal. For example, the list [1, 1] + * will have only one permutation, instead of two. This is why the elements + * have to implement {@link Comparable}. + * + *

An empty iterable has only one permutation, which is an empty list. + * + *

This method is equivalent to + * {@code Collections2.orderedPermutations(list, Ordering.natural())}. + * + * @param elements the original iterable whose elements have to be permuted. + * @return an immutable {@link Collection} containing all the different + * permutations of the original iterable. + * @throws NullPointerException if the specified iterable is null or has any + * null elements. + * @since 12.0 + */ + @Beta public static > + Collection> orderedPermutations(Iterable elements) { + return orderedPermutations(elements, Ordering.natural()); + } + + /** + * Returns a {@link Collection} of all the permutations of the specified + * {@link Iterable} using the specified {@link Comparator} for establishing + * the lexicographical ordering. + * + *

Examples:

   {@code
+   *
+   *   for (List perm : orderedPermutations(asList("b", "c", "a"))) {
+   *     println(perm);
+   *   }
+   *   // -> ["a", "b", "c"]
+   *   // -> ["a", "c", "b"]
+   *   // -> ["b", "a", "c"]
+   *   // -> ["b", "c", "a"]
+   *   // -> ["c", "a", "b"]
+   *   // -> ["c", "b", "a"]
+   *
+   *   for (List perm : orderedPermutations(asList(1, 2, 2, 1))) {
+   *     println(perm);
+   *   }
+   *   // -> [1, 1, 2, 2]
+   *   // -> [1, 2, 1, 2]
+   *   // -> [1, 2, 2, 1]
+   *   // -> [2, 1, 1, 2]
+   *   // -> [2, 1, 2, 1]
+   *   // -> [2, 2, 1, 1]}
+ * + *

Notes: This is an implementation of the algorithm for + * Lexicographical Permutations Generation, described in Knuth's "The Art of + * Computer Programming", Volume 4, Chapter 7, Section 7.2.1.2. The + * iteration order follows the lexicographical order. This means that + * the first permutation will be in ascending order, and the last will be in + * descending order. + * + *

Elements that compare equal are considered equal and no new permutations + * are created by swapping them. + * + *

An empty iterable has only one permutation, which is an empty list. + * + * @param elements the original iterable whose elements have to be permuted. + * @param comparator a comparator for the iterable's elements. + * @return an immutable {@link Collection} containing all the different + * permutations of the original iterable. + * @throws NullPointerException If the specified iterable is null, has any + * null elements, or if the specified comparator is null. + * @since 12.0 + */ + @Beta public static Collection> orderedPermutations( + Iterable elements, Comparator comparator) { + return new OrderedPermutationCollection(elements, comparator); + } + + private static final class OrderedPermutationCollection + extends AbstractCollection> { + final ImmutableList inputList; + final Comparator comparator; + final int size; + + OrderedPermutationCollection(Iterable input, + Comparator comparator) { + this.inputList = Ordering.from(comparator).immutableSortedCopy(input); + this.comparator = comparator; + this.size = calculateSize(inputList, comparator); + } + + /** + * The number of permutations with repeated elements is calculated as + * follows: + *

    + *
  • For an empty list, it is 1 (base case).
  • + *
  • When r numbers are added to a list of n-r elements, the number of + * permutations is increased by a factor of (n choose r).
  • + *
+ */ + private static int calculateSize( + List sortedInputList, Comparator comparator) { + long permutations = 1; + int n = 1; + int r = 1; + while (n < sortedInputList.size()) { + int comparison = comparator.compare( + sortedInputList.get(n - 1), sortedInputList.get(n)); + if (comparison < 0) { + // We move to the next non-repeated element. + permutations *= binomial(n, r); + r = 0; + if (!isPositiveInt(permutations)) { + return Integer.MAX_VALUE; + } + } + n++; + r++; + } + permutations *= binomial(n, r); + if (!isPositiveInt(permutations)) { + return Integer.MAX_VALUE; + } + return (int) permutations; + } + + @Override public int size() { + return size; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Iterator> iterator() { + return new OrderedPermutationIterator(inputList, comparator); + } + + @Override public boolean contains(@Nullable Object obj) { + if (obj instanceof List) { + List list = (List) obj; + return isPermutation(inputList, list); + } + return false; + } + + @Override public String toString() { + return "orderedPermutationCollection(" + inputList + ")"; + } + } + + private static final class OrderedPermutationIterator + extends AbstractIterator> { + + List nextPermutation; + final Comparator comparator; + + OrderedPermutationIterator(List list, + Comparator comparator) { + this.nextPermutation = Lists.newArrayList(list); + this.comparator = comparator; + } + + @Override protected List computeNext() { + if (nextPermutation == null) { + return endOfData(); + } + ImmutableList next = ImmutableList.copyOf(nextPermutation); + calculateNextPermutation(); + return next; + } + + void calculateNextPermutation() { + int j = findNextJ(); + if (j == -1) { + nextPermutation = null; + return; + } + + int l = findNextL(j); + Collections.swap(nextPermutation, j, l); + int n = nextPermutation.size(); + Collections.reverse(nextPermutation.subList(j + 1, n)); + } + + int findNextJ() { + for (int k = nextPermutation.size() - 2; k >= 0; k--) { + if (comparator.compare(nextPermutation.get(k), + nextPermutation.get(k + 1)) < 0) { + return k; + } + } + return -1; + } + + int findNextL(int j) { + E ak = nextPermutation.get(j); + for (int l = nextPermutation.size() - 1; l > j; l--) { + if (comparator.compare(ak, nextPermutation.get(l)) < 0) { + return l; + } + } + throw new AssertionError("this statement should be unreachable"); + } + } + + /** + * Returns a {@link Collection} of all the permutations of the specified + * {@link Collection}. + * + *

Notes: This is an implementation of the Plain Changes algorithm + * for permutations generation, described in Knuth's "The Art of Computer + * Programming", Volume 4, Chapter 7, Section 7.2.1.2. + * + *

If the input list contains equal elements, some of the generated + * permutations will be equal. + * + *

An empty collection has only one permutation, which is an empty list. + * + * @param elements the original collection whose elements have to be permuted. + * @return an immutable {@link Collection} containing all the different + * permutations of the original collection. + * @throws NullPointerException if the specified collection is null or has any + * null elements. + * @since 12.0 + */ + @Beta public static Collection> permutations( + Collection elements) { + return new PermutationCollection(ImmutableList.copyOf(elements)); + } + + private static final class PermutationCollection + extends AbstractCollection> { + final ImmutableList inputList; + + PermutationCollection(ImmutableList input) { + this.inputList = input; + } + + @Override public int size() { + return IntMath.factorial(inputList.size()); + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Iterator> iterator() { + return new PermutationIterator(inputList); + } + + @Override public boolean contains(@Nullable Object obj) { + if (obj instanceof List) { + List list = (List) obj; + return isPermutation(inputList, list); + } + return false; + } + + @Override public String toString() { + return "permutations(" + inputList + ")"; + } + } + + private static class PermutationIterator + extends AbstractIterator> { + final List list; + final int[] c; + final int[] o; + int j; + + PermutationIterator(List list) { + this.list = new ArrayList(list); + int n = list.size(); + c = new int[n]; + o = new int[n]; + for (int i = 0; i < n; i++) { + c[i] = 0; + o[i] = 1; + } + j = Integer.MAX_VALUE; + } + + @Override protected List computeNext() { + if (j <= 0) { + return endOfData(); + } + ImmutableList next = ImmutableList.copyOf(list); + calculateNextPermutation(); + return next; + } + + void calculateNextPermutation() { + j = list.size() - 1; + int s = 0; + + // Handle the special case of an empty list. Skip the calculation of the + // next permutation. + if (j == -1) { + return; + } + + while (true) { + int q = c[j] + o[j]; + if (q < 0) { + switchDirection(); + continue; + } + if (q == j + 1) { + if (j == 0) { + break; + } + s++; + switchDirection(); + continue; + } + + Collections.swap(list, j - c[j] + s, j - q + s); + c[j] = q; + break; + } + } + + void switchDirection() { + o[j] = -o[j]; + j--; + } + } + + /** + * Returns {@code true} if the second list is a permutation of the first. + */ + private static boolean isPermutation(List first, + List second) { + if (first.size() != second.size()) { + return false; + } + Multiset firstSet = HashMultiset.create(first); + Multiset secondSet = HashMultiset.create(second); + return firstSet.equals(secondSet); + } + + private static boolean isPositiveInt(long n) { + return n >= 0 && n <= Integer.MAX_VALUE; + } +} diff --git a/guava/src/com/google/common/collect/ComparatorOrdering.java b/guava/src/com/google/common/collect/ComparatorOrdering.java new file mode 100644 index 0000000..5eb7612 --- /dev/null +++ b/guava/src/com/google/common/collect/ComparatorOrdering.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javax.annotation.Nullable; + +/** An ordering for a pre-existing comparator. */ +@GwtCompatible(serializable = true) +final class ComparatorOrdering extends Ordering implements Serializable { + final Comparator comparator; + + ComparatorOrdering(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + @Override public int compare(T a, T b) { + return comparator.compare(a, b); + } + + // Override just to remove a level of indirection from inner loops + @Override public int binarySearch(List sortedList, T key) { + return Collections.binarySearch(sortedList, key, comparator); + } + + // Override just to remove a level of indirection from inner loops + @Override public List sortedCopy(Iterable iterable) { + List list = Lists.newArrayList(iterable); + Collections.sort(list, comparator); + return list; + } + + // Override just to remove a level of indirection from inner loops + @Override public ImmutableList immutableSortedCopy(Iterable iterable) { + @SuppressWarnings("unchecked") // we'll only ever have E's in here + E[] elements = (E[]) Iterables.toArray(iterable); + for (E e : elements) { + checkNotNull(e); + } + Arrays.sort(elements, comparator); + return ImmutableList.asImmutableList(elements); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ComparatorOrdering) { + ComparatorOrdering that = (ComparatorOrdering) object; + return this.comparator.equals(that.comparator); + } + return false; + } + + @Override public int hashCode() { + return comparator.hashCode(); + } + + @Override public String toString() { + return comparator.toString(); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ComparisonChain.java b/guava/src/com/google/common/collect/ComparisonChain.java new file mode 100644 index 0000000..2ed8cc4 --- /dev/null +++ b/guava/src/com/google/common/collect/ComparisonChain.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Booleans; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +import java.util.Comparator; + +import javax.annotation.Nullable; + +/** + * A utility for performing a "lazy" chained comparison statement, which + * performs comparisons only until it finds a nonzero result. For example: + *

   {@code
+ *
+ *   public int compareTo(Foo that) {
+ *     return ComparisonChain.start()
+ *         .compare(this.aString, that.aString)
+ *         .compare(this.anInt, that.anInt)
+ *         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
+ *         .result();
+ *   }}
+ * + * The value of this expression will have the same sign as the first + * nonzero comparison result in the chain, or will be zero if every + * comparison result was zero. + * + *

Once any comparison returns a nonzero value, remaining comparisons are + * "short-circuited". + * + *

See the Guava User Guide article on + * {@code ComparisonChain}. + * + * @author Mark Davis + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public abstract class ComparisonChain { + private ComparisonChain() {} + + /** + * Begins a new chained comparison statement. See example in the class + * documentation. + */ + public static ComparisonChain start() { + return ACTIVE; + } + + private static final ComparisonChain ACTIVE = new ComparisonChain() { + @SuppressWarnings("unchecked") + @Override public ComparisonChain compare( + Comparable left, Comparable right) { + return classify(left.compareTo(right)); + } + @Override public ComparisonChain compare( + @Nullable T left, @Nullable T right, Comparator comparator) { + return classify(comparator.compare(left, right)); + } + @Override public ComparisonChain compare(int left, int right) { + return classify(Ints.compare(left, right)); + } + @Override public ComparisonChain compare(long left, long right) { + return classify(Longs.compare(left, right)); + } + @Override public ComparisonChain compare(float left, float right) { + return classify(Float.compare(left, right)); + } + @Override public ComparisonChain compare(double left, double right) { + return classify(Double.compare(left, right)); + } + @Override public ComparisonChain compareTrueFirst(boolean left, boolean right) { + return classify(Booleans.compare(right, left)); // reversed + } + @Override public ComparisonChain compareFalseFirst(boolean left, boolean right) { + return classify(Booleans.compare(left, right)); + } + ComparisonChain classify(int result) { + return (result < 0) ? LESS : (result > 0) ? GREATER : ACTIVE; + } + @Override public int result() { + return 0; + } + }; + + private static final ComparisonChain LESS = new InactiveComparisonChain(-1); + + private static final ComparisonChain GREATER = new InactiveComparisonChain(1); + + private static final class InactiveComparisonChain extends ComparisonChain { + final int result; + + InactiveComparisonChain(int result) { + this.result = result; + } + @Override public ComparisonChain compare( + @Nullable Comparable left, @Nullable Comparable right) { + return this; + } + @Override public ComparisonChain compare(@Nullable T left, + @Nullable T right, @Nullable Comparator comparator) { + return this; + } + @Override public ComparisonChain compare(int left, int right) { + return this; + } + @Override public ComparisonChain compare(long left, long right) { + return this; + } + @Override public ComparisonChain compare(float left, float right) { + return this; + } + @Override public ComparisonChain compare(double left, double right) { + return this; + } + @Override public ComparisonChain compareTrueFirst(boolean left, boolean right) { + return this; + } + @Override public ComparisonChain compareFalseFirst(boolean left, boolean right) { + return this; + } + @Override public int result() { + return result; + } + } + + /** + * Compares two comparable objects as specified by {@link + * Comparable#compareTo}, if the result of this comparison chain + * has not already been determined. + */ + public abstract ComparisonChain compare( + Comparable left, Comparable right); + + /** + * Compares two objects using a comparator, if the result of this + * comparison chain has not already been determined. + */ + public abstract ComparisonChain compare( + @Nullable T left, @Nullable T right, Comparator comparator); + + /** + * Compares two {@code int} values as specified by {@link Ints#compare}, + * if the result of this comparison chain has not already been + * determined. + */ + public abstract ComparisonChain compare(int left, int right); + + /** + * Compares two {@code long} values as specified by {@link Longs#compare}, + * if the result of this comparison chain has not already been + * determined. + */ + public abstract ComparisonChain compare(long left, long right); + + /** + * Compares two {@code float} values as specified by {@link + * Float#compare}, if the result of this comparison chain has not + * already been determined. + */ + public abstract ComparisonChain compare(float left, float right); + + /** + * Compares two {@code double} values as specified by {@link + * Double#compare}, if the result of this comparison chain has not + * already been determined. + */ + public abstract ComparisonChain compare(double left, double right); + + /** + * Compares two {@code boolean} values, considering {@code true} to be less + * than {@code false}, if the result of this comparison chain has not + * already been determined. + * + * @since 12.0 + */ + public abstract ComparisonChain compareTrueFirst(boolean left, boolean right); + + /** + * Compares two {@code boolean} values, considering {@code false} to be less + * than {@code true}, if the result of this comparison chain has not + * already been determined. + * + * @since 12.0 (present as {@code compare} since 2.0) + */ + public abstract ComparisonChain compareFalseFirst(boolean left, boolean right); + + /** + * Old name of {@link #compareFalseFirst}. + * + * @deprecated Use {@link #compareFalseFirst}; or, if the parameters passed + * are being either negated or reversed, undo the negation or reversal and + * use {@link #compareTrueFirst}. This method is scheduled for deletion + * in September 2013. + */ + @Deprecated + public final ComparisonChain compare(boolean left, boolean right) { + return compareFalseFirst(left, right); + } + + /** + * Ends this comparison chain and returns its result: a value having the + * same sign as the first nonzero comparison result in the chain, or zero if + * every result was zero. + */ + public abstract int result(); +} diff --git a/guava/src/com/google/common/collect/CompoundOrdering.java b/guava/src/com/google/common/collect/CompoundOrdering.java new file mode 100644 index 0000000..26ebf54 --- /dev/null +++ b/guava/src/com/google/common/collect/CompoundOrdering.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Comparator; + +/** An ordering that tries several comparators in order. */ +@GwtCompatible(serializable = true) +final class CompoundOrdering extends Ordering implements Serializable { + final ImmutableList> comparators; + + CompoundOrdering(Comparator primary, + Comparator secondary) { + this.comparators + = ImmutableList.>of(primary, secondary); + } + + CompoundOrdering(Iterable> comparators) { + this.comparators = ImmutableList.copyOf(comparators); + } + + @Override public int compare(T left, T right) { + // Avoid using the Iterator to avoid generating garbage (issue 979). + int size = comparators.size(); + for (int i = 0; i < size; i++) { + int result = comparators.get(i).compare(left, right); + if (result != 0) { + return result; + } + } + return 0; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CompoundOrdering) { + CompoundOrdering that = (CompoundOrdering) object; + return this.comparators.equals(that.comparators); + } + return false; + } + + @Override public int hashCode() { + return comparators.hashCode(); + } + + @Override public String toString() { + return "Ordering.compound(" + comparators + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ComputationException.java b/guava/src/com/google/common/collect/ComputationException.java new file mode 100644 index 0000000..5401aff --- /dev/null +++ b/guava/src/com/google/common/collect/ComputationException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Wraps an exception that occurred during a computation. + * + * @author Bob Lee + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public class ComputationException extends RuntimeException { + /** + * Creates a new instance with the given cause. + */ + public ComputationException(Throwable cause) { + super(cause); + } + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ComputingConcurrentHashMap.java b/guava/src/com/google/common/collect/ComputingConcurrentHashMap.java new file mode 100644 index 0000000..fce866d --- /dev/null +++ b/guava/src/com/google/common/collect/ComputingConcurrentHashMap.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.common.collect.MapMaker.RemovalCause; +import com.google.common.collect.MapMaker.RemovalListener; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * Adds computing functionality to {@link MapMakerInternalMap}. + * + * @author Bob Lee + * @author Charles Fry + */ +class ComputingConcurrentHashMap extends MapMakerInternalMap { + final Function computingFunction; + + /** + * Creates a new, empty map with the specified strategy, initial capacity, load factor and + * concurrency level. + */ + ComputingConcurrentHashMap(MapMaker builder, + Function computingFunction) { + super(builder); + this.computingFunction = checkNotNull(computingFunction); + } + + @Override + Segment createSegment(int initialCapacity, int maxSegmentSize) { + return new ComputingSegment(this, initialCapacity, maxSegmentSize); + } + + @Override + ComputingSegment segmentFor(int hash) { + return (ComputingSegment) super.segmentFor(hash); + } + + V getOrCompute(K key) throws ExecutionException { + int hash = hash(checkNotNull(key)); + return segmentFor(hash).getOrCompute(key, hash, computingFunction); + } + + @SuppressWarnings("serial") // This class is never serialized. + static final class ComputingSegment extends Segment { + ComputingSegment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + V getOrCompute(K key, int hash, Function computingFunction) + throws ExecutionException { + try { + outer: while (true) { + // don't call getLiveEntry, which would ignore computing values + ReferenceEntry e = getEntry(key, hash); + if (e != null) { + V value = getLiveValue(e); + if (value != null) { + recordRead(e); + return value; + } + } + + // at this point e is either null, computing, or expired; + // avoid locking if it's already computing + if (e == null || !e.getValueReference().isComputingReference()) { + boolean createNewEntry = true; + ComputingValueReference computingValueReference = null; + lock(); + try { + preWriteCleanup(); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + if (valueReference.isComputingReference()) { + createNewEntry = false; + } else { + V value = e.getValueReference().get(); + if (value == null) { + enqueueNotification(entryKey, hash, value, RemovalCause.COLLECTED); + } else if (map.expires() && map.isExpired(e)) { + // This is a duplicate check, as preWriteCleanup already purged expired + // entries, but let's accomodate an incorrect expiration queue. + enqueueNotification(entryKey, hash, value, RemovalCause.EXPIRED); + } else { + recordLockedRead(e); + return value; + } + + // immediately reuse invalid entries + evictionQueue.remove(e); + expirationQueue.remove(e); + this.count = newCount; // write-volatile + } + break; + } + } + + if (createNewEntry) { + computingValueReference = new ComputingValueReference(computingFunction); + + if (e == null) { + e = newEntry(key, hash, first); + e.setValueReference(computingValueReference); + table.set(index, e); + } else { + e.setValueReference(computingValueReference); + } + } + } finally { + unlock(); + postWriteCleanup(); + } + + if (createNewEntry) { + // This thread solely created the entry. + return compute(key, hash, e, computingValueReference); + } + } + + // The entry already exists. Wait for the computation. + checkState(!Thread.holdsLock(e), "Recursive computation"); + // don't consider expiration as we're concurrent with computation + V value = e.getValueReference().waitForValue(); + if (value != null) { + recordRead(e); + return value; + } + // else computing thread will clearValue + continue outer; + } + } finally { + postReadCleanup(); + } + } + + V compute(K key, int hash, ReferenceEntry e, + ComputingValueReference computingValueReference) + throws ExecutionException { + V value = null; + long start = System.nanoTime(); + long end = 0; + try { + // Synchronizes on the entry to allow failing fast when a recursive computation is + // detected. This is not fool-proof since the entry may be copied when the segment + // is written to. + synchronized (e) { + value = computingValueReference.compute(key, hash); + end = System.nanoTime(); + } + if (value != null) { + // putIfAbsent + V oldValue = put(key, hash, value, true); + if (oldValue != null) { + // the computed value was already clobbered + enqueueNotification(key, hash, value, RemovalCause.REPLACED); + } + } + return value; + } finally { + if (end == 0) { + end = System.nanoTime(); + } + if (value == null) { + clearValue(key, hash, computingValueReference); + } + } + } + } + + /** + * Used to provide computation exceptions to other threads. + */ + private static final class ComputationExceptionReference implements ValueReference { + final Throwable t; + + ComputationExceptionReference(Throwable t) { + this.t = t; + } + + @Override + public V get() { + return null; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public V waitForValue() throws ExecutionException { + throw new ExecutionException(t); + } + + @Override + public void clear(ValueReference newValue) {} + } + + /** + * Used to provide computation result to other threads. + */ + private static final class ComputedReference implements ValueReference { + final V value; + + ComputedReference(@Nullable V value) { + this.value = value; + } + + @Override + public V get() { + return value; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public V waitForValue() { + return get(); + } + + @Override + public void clear(ValueReference newValue) {} + } + + private static final class ComputingValueReference implements ValueReference { + final Function computingFunction; + + @GuardedBy("ComputingValueReference.this") // writes + volatile ValueReference computedReference = unset(); + + public ComputingValueReference(Function computingFunction) { + this.computingFunction = computingFunction; + } + + @Override + public V get() { + // All computation lookups go through waitForValue. This method thus is + // only used by put, to whom we always want to appear absent. + return null; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isComputingReference() { + return true; + } + + /** + * Waits for a computation to complete. Returns the result of the computation. + */ + @Override + public V waitForValue() throws ExecutionException { + if (computedReference == UNSET) { + boolean interrupted = false; + try { + synchronized (this) { + while (computedReference == UNSET) { + try { + wait(); + } catch (InterruptedException ie) { + interrupted = true; + } + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + return computedReference.waitForValue(); + } + + @Override + public void clear(ValueReference newValue) { + // The pending computation was clobbered by a manual write. Unblock all + // pending gets, and have them return the new value. + setValueReference(newValue); + + // TODO(fry): could also cancel computation if we had a thread handle + } + + V compute(K key, int hash) throws ExecutionException { + V value; + try { + value = computingFunction.apply(key); + } catch (Throwable t) { + setValueReference(new ComputationExceptionReference(t)); + throw new ExecutionException(t); + } + + setValueReference(new ComputedReference(value)); + return value; + } + + void setValueReference(ValueReference valueReference) { + synchronized (this) { + if (computedReference == UNSET) { + computedReference = valueReference; + notifyAll(); + } + } + } + } + + /** + * Overrides get() to compute on demand. Also throws an exception when {@code null} is returned + * from a computation. + */ + static final class ComputingMapAdapter + extends ComputingConcurrentHashMap implements Serializable { + private static final long serialVersionUID = 0; + + ComputingMapAdapter(MapMaker mapMaker, + Function computingFunction) { + super(mapMaker, computingFunction); + } + + @SuppressWarnings("unchecked") // unsafe, which is one advantage of Cache over Map + @Override + public V get(Object key) { + V value; + try { + value = getOrCompute((K) key); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + Throwables.propagateIfInstanceOf(cause, ComputationException.class); + throw new ComputationException(cause); + } + + if (value == null) { + throw new NullPointerException(computingFunction + " returned null for key " + key + "."); + } + return value; + } + } + + // Serialization Support + + private static final long serialVersionUID = 4; + + @Override + Object writeReplace() { + return new ComputingSerializationProxy(keyStrength, valueStrength, keyEquivalence, + valueEquivalence, expireAfterWriteNanos, expireAfterAccessNanos, maximumSize, + concurrencyLevel, removalListener, this, computingFunction); + } + + static final class ComputingSerializationProxy extends AbstractSerializationProxy { + + final Function computingFunction; + + ComputingSerializationProxy(Strength keyStrength, Strength valueStrength, + Equivalence keyEquivalence, Equivalence valueEquivalence, + long expireAfterWriteNanos, long expireAfterAccessNanos, int maximumSize, + int concurrencyLevel, RemovalListener removalListener, + ConcurrentMap delegate, Function computingFunction) { + super(keyStrength, valueStrength, keyEquivalence, valueEquivalence, expireAfterWriteNanos, + expireAfterAccessNanos, maximumSize, concurrencyLevel, removalListener, delegate); + this.computingFunction = computingFunction; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + writeMapTo(out); + } + + @SuppressWarnings("deprecation") // self-use + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + MapMaker mapMaker = readMapMaker(in); + delegate = mapMaker.makeComputingMap(computingFunction); + readEntries(in); + } + + Object readResolve() { + return delegate; + } + + private static final long serialVersionUID = 4; + } +} diff --git a/guava/src/com/google/common/collect/ConcurrentHashMultiset.java b/guava/src/com/google/common/collect/ConcurrentHashMultiset.java new file mode 100644 index 0000000..b96ed8f --- /dev/null +++ b/guava/src/com/google/common/collect/ConcurrentHashMultiset.java @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Multisets.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Serialization.FieldSetter; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * A multiset that supports concurrent modifications and that provides atomic versions of most + * {@code Multiset} operations (exceptions where noted). Null elements are not supported. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Cliff L. Biffle + * @author mike nonemacher + * @since 2.0 (imported from Google Collections Library) + */ +public final class ConcurrentHashMultiset extends AbstractMultiset implements Serializable { + + /* + * The ConcurrentHashMultiset's atomic operations are implemented primarily in terms of + * AtomicInteger's atomic operations, with some help from ConcurrentMap's atomic operations on + * creation and removal (including automatic removal of zeroes). If the modification of an + * AtomicInteger results in zero, we compareAndSet the value to zero; if that succeeds, we remove + * the entry from the Map. If another operation sees a zero in the map, it knows that the entry is + * about to be removed, so this operation may remove it (often by replacing it with a new + * AtomicInteger). + */ + + /** The number of occurrences of each element. */ + private final transient ConcurrentMap countMap; + + // This constant allows the deserialization code to set a final field. This holder class + // makes sure it is not initialized unless an instance is deserialized. + private static class FieldSettersHolder { + static final FieldSetter COUNT_MAP_FIELD_SETTER = + Serialization.getFieldSetter(ConcurrentHashMultiset.class, "countMap"); + } + + /** + * Creates a new, empty {@code ConcurrentHashMultiset} using the default + * initial capacity, load factor, and concurrency settings. + */ + public static ConcurrentHashMultiset create() { + // TODO(schmoe): provide a way to use this class with other (possibly arbitrary) + // ConcurrentMap implementors. One possibility is to extract most of this class into + // an AbstractConcurrentMapMultiset. + return new ConcurrentHashMultiset(new ConcurrentHashMap()); + } + + /** + * Creates a new {@code ConcurrentHashMultiset} containing the specified elements, using + * the default initial capacity, load factor, and concurrency settings. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static ConcurrentHashMultiset create(Iterable elements) { + ConcurrentHashMultiset multiset = ConcurrentHashMultiset.create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + /** + * Creates a new, empty {@code ConcurrentHashMultiset} using {@code mapMaker} + * to construct the internal backing map. + * + *

If this {@link MapMaker} is configured to use entry eviction of any kind, this eviction + * applies to all occurrences of a given element as a single unit. However, most updates to the + * multiset do not count as map updates at all, since we're usually just mutating the value + * stored in the map, so {@link MapMaker#expireAfterAccess} makes sense (evict the entry that + * was queried or updated longest ago), but {@link MapMaker#expireAfterWrite} doesn't, because + * the eviction time is measured from when we saw the first occurrence of the object. + * + *

The returned multiset is serializable but any serialization caveats + * given in {@code MapMaker} apply. + * + *

Finally, soft/weak values can be used but are not very useful: the values are created + * internally and not exposed externally, so no one else will have a strong reference to the + * values. Weak keys on the other hand can be useful in some scenarios. + * + * @since 7.0 + */ + @Beta + public static ConcurrentHashMultiset create( + GenericMapMaker mapMaker) { + return new ConcurrentHashMultiset(mapMaker.makeMap()); + } + + /** + * Creates an instance using {@code countMap} to store elements and their counts. + * + *

This instance will assume ownership of {@code countMap}, and other code + * should not maintain references to the map or modify it in any way. + * + * @param countMap backing map for storing the elements in the multiset and + * their counts. It must be empty. + * @throws IllegalArgumentException if {@code countMap} is not empty + */ + @VisibleForTesting ConcurrentHashMultiset(ConcurrentMap countMap) { + checkArgument(countMap.isEmpty()); + this.countMap = countMap; + } + + // Query Operations + + /** + * Returns the number of occurrences of {@code element} in this multiset. + * + * @param element the element to look for + * @return the nonnegative number of occurrences of the element + */ + @Override public int count(@Nullable Object element) { + AtomicInteger existingCounter = safeGet(element); + return (existingCounter == null) ? 0 : existingCounter.get(); + } + + /** + * Depending on the type of the underlying map, map.get may throw NullPointerException or + * ClassCastException, if the object is null or of the wrong type. We usually just want to treat + * those cases as if the element isn't in the map, by catching the exceptions and returning null. + */ + private AtomicInteger safeGet(Object element) { + try { + return countMap.get(element); + } catch (NullPointerException e) { + return null; + } catch (ClassCastException e) { + return null; + } + } + + /** + * {@inheritDoc} + * + *

If the data in the multiset is modified by any other threads during this method, + * it is undefined which (if any) of these modifications will be reflected in the result. + */ + @Override public int size() { + long sum = 0L; + for (AtomicInteger value : countMap.values()) { + sum += value.get(); + } + return Ints.saturatedCast(sum); + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List snapshot() { + List list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry entry : entrySet()) { + E element = entry.getElement(); + for (int i = entry.getCount(); i > 0; i--) { + list.add(element); + } + } + return list; + } + + // Modification Operations + + /** + * Adds a number of occurrences of the specified element to this multiset. + * + * @param element the element to add + * @param occurrences the number of occurrences to add + * @return the previous count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * the resulting amount would exceed {@link Integer#MAX_VALUE} + */ + @Override public int add(E element, int occurrences) { + checkNotNull(element); + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + AtomicInteger existingCounter = safeGet(element); + if (existingCounter == null) { + existingCounter = countMap.putIfAbsent(element, new AtomicInteger(occurrences)); + if (existingCounter == null) { + return 0; + } + // existingCounter != null: fall through to operate against the existing AtomicInteger + } + + while (true) { + int oldValue = existingCounter.get(); + if (oldValue != 0) { + try { + int newValue = IntMath.checkedAdd(oldValue, occurrences); + if (existingCounter.compareAndSet(oldValue, newValue)) { + // newValue can't == 0, so no need to check & remove + return oldValue; + } + } catch (ArithmeticException overflow) { + throw new IllegalArgumentException("Overflow adding " + occurrences + + " occurrences to a count of " + oldValue); + } + } else { + // In the case of a concurrent remove, we might observe a zero value, which means another + // thread is about to remove (element, existingCounter) from the map. Rather than wait, + // we can just do that work here. + AtomicInteger newCounter = new AtomicInteger(occurrences); + if ((countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter)) { + return 0; + } + break; + } + } + + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes a number of occurrences of the specified element from this multiset. If the multiset + * contains fewer than this number of occurrences to begin with, all occurrences will be removed. + * + * @param element the element whose occurrences should be removed + * @param occurrences the number of occurrences of the element to remove + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + /* + * TODO(cpovirk): remove and removeExactly currently accept null inputs only + * if occurrences == 0. This satisfies both NullPointerTester and + * CollectionRemoveTester.testRemove_nullAllowed, but it's not clear that it's + * a good policy, especially because, in order for the test to pass, the + * parameter must be misleadingly annotated as @Nullable. I suspect that + * we'll want to remove @Nullable, add an eager checkNotNull, and loosen up + * testRemove_nullAllowed. + */ + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + AtomicInteger existingCounter = safeGet(element); + if (existingCounter == null) { + return 0; + } + while (true) { + int oldValue = existingCounter.get(); + if (oldValue != 0) { + int newValue = Math.max(0, oldValue - occurrences); + if (existingCounter.compareAndSet(oldValue, newValue)) { + if (newValue == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return oldValue; + } + } else { + return 0; + } + } + } + + /** + * Removes exactly the specified number of occurrences of {@code element}, or makes no + * change if this is not possible. + * + *

This method, in contrast to {@link #remove(Object, int)}, has no effect when the + * element count is smaller than {@code occurrences}. + * + * @param element the element to remove + * @param occurrences the number of occurrences of {@code element} to remove + * @return {@code true} if the removal was possible (including if {@code occurrences} is zero) + */ + public boolean removeExactly(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return true; + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + AtomicInteger existingCounter = safeGet(element); + if (existingCounter == null) { + return false; + } + while (true) { + int oldValue = existingCounter.get(); + if (oldValue < occurrences) { + return false; + } + int newValue = oldValue - occurrences; + if (existingCounter.compareAndSet(oldValue, newValue)) { + if (newValue == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return true; + } + } + } + + /** + * Adds or removes occurrences of {@code element} such that the {@link #count} of the + * element becomes {@code count}. + * + * @return the count of {@code element} in the multiset before this call + * @throws IllegalArgumentException if {@code count} is negative + */ + @Override public int setCount(E element, int count) { + checkNotNull(element); + checkNonnegative(count, "count"); + while (true) { + AtomicInteger existingCounter = safeGet(element); + if (existingCounter == null) { + if (count == 0) { + return 0; + } else { + existingCounter = countMap.putIfAbsent(element, new AtomicInteger(count)); + if (existingCounter == null) { + return 0; + } + // existingCounter != null: fall through + } + } + + while (true) { + int oldValue = existingCounter.get(); + if (oldValue == 0) { + if (count == 0) { + return 0; + } else { + AtomicInteger newCounter = new AtomicInteger(count); + if ((countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter)) { + return 0; + } + } + break; + } else { + if (existingCounter.compareAndSet(oldValue, count)) { + if (count == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return oldValue; + } + } + } + } + } + + /** + * Sets the number of occurrences of {@code element} to {@code newCount}, but only if + * the count is currently {@code expectedOldCount}. If {@code element} does not appear + * in the multiset exactly {@code expectedOldCount} times, no changes will be made. + * + * @return {@code true} if the change was successful. This usually indicates + * that the multiset has been modified, but not always: in the case that + * {@code expectedOldCount == newCount}, the method will return {@code true} if + * the condition was met. + * @throws IllegalArgumentException if {@code expectedOldCount} or {@code newCount} is negative + */ + @Override public boolean setCount(E element, int expectedOldCount, int newCount) { + checkNotNull(element); + checkNonnegative(expectedOldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + AtomicInteger existingCounter = safeGet(element); + if (existingCounter == null) { + if (expectedOldCount != 0) { + return false; + } else if (newCount == 0) { + return true; + } else { + // if our write lost the race, it must have lost to a nonzero value, so we can stop + return countMap.putIfAbsent(element, new AtomicInteger(newCount)) == null; + } + } + int oldValue = existingCounter.get(); + if (oldValue == expectedOldCount) { + if (oldValue == 0) { + if (newCount == 0) { + // Just observed a 0; try to remove the entry to clean up the map + countMap.remove(element, existingCounter); + return true; + } else { + AtomicInteger newCounter = new AtomicInteger(newCount); + return (countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter); + } + } else { + if (existingCounter.compareAndSet(oldValue, newCount)) { + if (newCount == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return true; + } + } + } + return false; + } + + // Views + + @Override Set createElementSet() { + final Set delegate = countMap.keySet(); + return new ForwardingSet() { + @Override protected Set delegate() { + return delegate; + } + @Override public boolean remove(Object object) { + try { + return delegate.remove(object); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + @Override public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + }; + } + + private transient EntrySet entrySet; + + @Override public Set> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + @Override int distinctElements() { + return countMap.size(); + } + + @Override public boolean isEmpty() { + return countMap.isEmpty(); + } + + @Override Iterator> entryIterator() { + // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support + // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it. + final Iterator> readOnlyIterator = + new AbstractIterator>() { + private Iterator> mapEntries = countMap.entrySet().iterator(); + + @Override protected Entry computeNext() { + while (true) { + if (!mapEntries.hasNext()) { + return endOfData(); + } + Map.Entry mapEntry = mapEntries.next(); + int count = mapEntry.getValue().get(); + if (count != 0) { + return Multisets.immutableEntry(mapEntry.getKey(), count); + } + } + } + }; + + return new ForwardingIterator>() { + private Entry last; + + @Override protected Iterator> delegate() { + return readOnlyIterator; + } + + @Override public Entry next() { + last = super.next(); + return last; + } + + @Override public void remove() { + checkState(last != null); + ConcurrentHashMultiset.this.setCount(last.getElement(), 0); + last = null; + } + }; + } + + @Override public void clear() { + countMap.clear(); + } + + private class EntrySet extends AbstractMultiset.EntrySet { + @Override ConcurrentHashMultiset multiset() { + return ConcurrentHashMultiset.this; + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + private List> snapshot() { + List> list = Lists.newArrayListWithExpectedSize(size()); + // Not Iterables.addAll(list, this), because that'll forward right back here. + Iterators.addAll(list, iterator()); + return list; + } + + @Override public boolean remove(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry entry = (Multiset.Entry) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + if (entryCount != 0) { + // Safe as long as we never add a new entry, which we won't. + @SuppressWarnings("unchecked") + Multiset multiset = (Multiset) multiset(); + return multiset.setCount(element, entryCount, 0); + } + } + return false; + } + } + + /** + * @serialData the ConcurrentMap of elements and their counts. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(countMap); + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + ConcurrentMap deserializedCountMap = + (ConcurrentMap) stream.readObject(); + FieldSettersHolder.COUNT_MAP_FIELD_SETTER.set(this, deserializedCountMap); + } + + private static final long serialVersionUID = 1; +} diff --git a/guava/src/com/google/common/collect/Constraint.java b/guava/src/com/google/common/collect/Constraint.java new file mode 100644 index 0000000..93b683b --- /dev/null +++ b/guava/src/com/google/common/collect/Constraint.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * A constraint that an element must satisfy in order to be added to a + * collection. For example, {@link Constraints#notNull()}, which prevents a + * collection from including any null elements, could be implemented like this: + *
   {@code
+ *
+ *   public Object checkElement(Object element) {
+ *     if (element == null) {
+ *       throw new NullPointerException();
+ *     }
+ *     return element;
+ *   }}
+ * + * In order to be effective, constraints should be deterministic; that is, + * they should not depend on state that can change (such as external state, + * random variables, and time) and should only depend on the value of the + * passed-in element. A non-deterministic constraint cannot reliably enforce + * that all the collection's elements meet the constraint, since the constraint + * is only enforced when elements are added. + * + * @see Constraints + * @see MapConstraint + * @author Mike Bostock + * @since 3.0 + */ +@Beta +@GwtCompatible +public interface Constraint { + /** + * Throws a suitable {@code RuntimeException} if the specified element is + * illegal. Typically this is either a {@link NullPointerException}, an + * {@link IllegalArgumentException}, or a {@link ClassCastException}, though + * an application-specific exception class may be used if appropriate. + * + * @param element the element to check + * @return the provided element + */ + E checkElement(E element); + + /** + * Returns a brief human readable description of this constraint, such as + * "Not null" or "Positive number". + */ + @Override + String toString(); +} diff --git a/guava/src/com/google/common/collect/Constraints.java b/guava/src/com/google/common/collect/Constraints.java new file mode 100644 index 0000000..3b7f8bb --- /dev/null +++ b/guava/src/com/google/common/collect/Constraints.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; + +/** + * Factories and utilities pertaining to the {@link Constraint} interface. + * + * @see MapConstraints + * @author Mike Bostock + * @author Jared Levy + * @since 3.0 + */ +@Beta +@GwtCompatible +public final class Constraints { + private Constraints() {} + + // enum singleton pattern + private enum NotNullConstraint implements Constraint { + INSTANCE; + + @Override + public Object checkElement(Object element) { + return checkNotNull(element); + } + + @Override public String toString() { + return "Not null"; + } + } + + /** + * Returns a constraint that verifies that the element is not null. If the + * element is null, a {@link NullPointerException} is thrown. + */ + // safe to narrow the type since checkElement returns its argument directly + @SuppressWarnings("unchecked") + public static Constraint notNull() { + return (Constraint) NotNullConstraint.INSTANCE; + } + + /** + * Returns a constrained view of the specified collection, using the specified + * constraint. Any operations that add new elements to the collection will + * call the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + *

The returned collection is not serializable. + * + * @param collection the collection to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the collection + */ + public static Collection constrainedCollection( + Collection collection, Constraint constraint) { + return new ConstrainedCollection(collection, constraint); + } + + /** @see Constraints#constrainedCollection */ + static class ConstrainedCollection extends ForwardingCollection { + private final Collection delegate; + private final Constraint constraint; + + public ConstrainedCollection( + Collection delegate, Constraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Collection delegate() { + return delegate; + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified set, using the specified + * constraint. Any operations that add new elements to the set will call the + * provided constraint. However, this method does not verify that existing + * elements satisfy the constraint. + * + *

The returned set is not serializable. + * + * @param set the set to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the set + */ + public static Set constrainedSet( + Set set, Constraint constraint) { + return new ConstrainedSet(set, constraint); + } + + /** @see Constraints#constrainedSet */ + static class ConstrainedSet extends ForwardingSet { + private final Set delegate; + private final Constraint constraint; + + public ConstrainedSet(Set delegate, Constraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Set delegate() { + return delegate; + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified sorted set, using the specified + * constraint. Any operations that add new elements to the sorted set will + * call the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + *

The returned set is not serializable. + * + * @param sortedSet the sorted set to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the sorted set + */ + public static SortedSet constrainedSortedSet( + SortedSet sortedSet, Constraint constraint) { + return new ConstrainedSortedSet(sortedSet, constraint); + } + + /** @see Constraints#constrainedSortedSet */ + private static class ConstrainedSortedSet extends ForwardingSortedSet { + final SortedSet delegate; + final Constraint constraint; + + ConstrainedSortedSet( + SortedSet delegate, Constraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected SortedSet delegate() { + return delegate; + } + @Override public SortedSet headSet(E toElement) { + return constrainedSortedSet(delegate.headSet(toElement), constraint); + } + @Override public SortedSet subSet(E fromElement, E toElement) { + return constrainedSortedSet( + delegate.subSet(fromElement, toElement), constraint); + } + @Override public SortedSet tailSet(E fromElement) { + return constrainedSortedSet(delegate.tailSet(fromElement), constraint); + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified list, using the specified + * constraint. Any operations that add new elements to the list will call the + * provided constraint. However, this method does not verify that existing + * elements satisfy the constraint. + * + *

If {@code list} implements {@link RandomAccess}, so will the returned + * list. The returned list is not serializable. + * + * @param list the list to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the list + */ + public static List constrainedList( + List list, Constraint constraint) { + return (list instanceof RandomAccess) + ? new ConstrainedRandomAccessList(list, constraint) + : new ConstrainedList(list, constraint); + } + + /** @see Constraints#constrainedList */ + @GwtCompatible + private static class ConstrainedList extends ForwardingList { + final List delegate; + final Constraint constraint; + + ConstrainedList(List delegate, Constraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected List delegate() { + return delegate; + } + + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public void add(int index, E element) { + constraint.checkElement(element); + delegate.add(index, element); + } + @Override public boolean addAll(Collection elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + @Override public boolean addAll(int index, Collection elements) + { + return delegate.addAll(index, checkElements(elements, constraint)); + } + @Override public ListIterator listIterator() { + return constrainedListIterator(delegate.listIterator(), constraint); + } + @Override public ListIterator listIterator(int index) { + return constrainedListIterator(delegate.listIterator(index), constraint); + } + @Override public E set(int index, E element) { + constraint.checkElement(element); + return delegate.set(index, element); + } + @Override public List subList(int fromIndex, int toIndex) { + return constrainedList( + delegate.subList(fromIndex, toIndex), constraint); + } + } + + /** @see Constraints#constrainedList */ + static class ConstrainedRandomAccessList extends ConstrainedList + implements RandomAccess { + ConstrainedRandomAccessList( + List delegate, Constraint constraint) { + super(delegate, constraint); + } + } + + /** + * Returns a constrained view of the specified list iterator, using the + * specified constraint. Any operations that would add new elements to the + * underlying list will be verified by the constraint. + * + * @param listIterator the iterator for which to return a constrained view + * @param constraint the constraint for elements in the list + * @return a constrained view of the specified iterator + */ + private static ListIterator constrainedListIterator( + ListIterator listIterator, Constraint constraint) { + return new ConstrainedListIterator(listIterator, constraint); + } + + /** @see Constraints#constrainedListIterator */ + static class ConstrainedListIterator extends ForwardingListIterator { + private final ListIterator delegate; + private final Constraint constraint; + + public ConstrainedListIterator( + ListIterator delegate, Constraint constraint) { + this.delegate = delegate; + this.constraint = constraint; + } + @Override protected ListIterator delegate() { + return delegate; + } + + @Override public void add(E element) { + constraint.checkElement(element); + delegate.add(element); + } + @Override public void set(E element) { + constraint.checkElement(element); + delegate.set(element); + } + } + + static Collection constrainedTypePreservingCollection( + Collection collection, Constraint constraint) { + if (collection instanceof SortedSet) { + return constrainedSortedSet((SortedSet) collection, constraint); + } else if (collection instanceof Set) { + return constrainedSet((Set) collection, constraint); + } else if (collection instanceof List) { + return constrainedList((List) collection, constraint); + } else { + return constrainedCollection(collection, constraint); + } + } + + /** + * Returns a constrained view of the specified multiset, using the specified + * constraint. Any operations that add new elements to the multiset will call + * the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + *

The returned multiset is not serializable. + * + * @param multiset the multiset to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the multiset + */ + public static Multiset constrainedMultiset( + Multiset multiset, Constraint constraint) { + return new ConstrainedMultiset(multiset, constraint); + } + + /** @see Constraints#constrainedMultiset */ + static class ConstrainedMultiset extends ForwardingMultiset { + private Multiset delegate; + private final Constraint constraint; + + public ConstrainedMultiset( + Multiset delegate, Constraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Multiset delegate() { + return delegate; + } + @Override public boolean add(E element) { + return standardAdd(element); + } + @Override public boolean addAll(Collection elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + @Override public int add(E element, int occurrences) { + constraint.checkElement(element); + return delegate.add(element, occurrences); + } + @Override public int setCount(E element, int count) { + constraint.checkElement(element); + return delegate.setCount(element, count); + } + @Override public boolean setCount(E element, int oldCount, int newCount) { + constraint.checkElement(element); + return delegate.setCount(element, oldCount, newCount); + } + } + + /* + * TODO(kevinb): For better performance, avoid making a copy of the elements + * by having addAll() call add() repeatedly instead. + */ + + private static Collection checkElements( + Collection elements, Constraint constraint) { + Collection copy = Lists.newArrayList(elements); + for (E element : copy) { + constraint.checkElement(element); + } + return copy; + } +} diff --git a/guava/src/com/google/common/collect/ContiguousSet.java b/guava/src/com/google/common/collect/ContiguousSet.java new file mode 100644 index 0000000..11be93e --- /dev/null +++ b/guava/src/com/google/common/collect/ContiguousSet.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A sorted set of contiguous values in a given {@link DiscreteDomain}. + * + *

Warning: Be extremely careful what you do with conceptually large instances (such as + * {@code ContiguousSet.create(Ranges.greaterThan(0), DiscreteDomains.integers()}). Certain + * operations on such a set can be performed efficiently, but others (such as {@link Set#hashCode} + * or {@link Collections#frequency}) can cause major performance problems. + * + * @author Gregory Kick + * @since 10.0 + */ +@Beta +@GwtCompatible(emulated = true) +@SuppressWarnings("rawtypes") // allow ungenerified Comparable types +public abstract class ContiguousSet extends ImmutableSortedSet { + /** + * Returns a {@code ContiguousSet} containing the same values in the given domain + * {@linkplain Range#contains contained} by the range. + * + * @throws IllegalArgumentException if neither range nor the domain has a lower bound, or if + * neither has an upper bound + * + * @since 13.0 + */ + public static ContiguousSet create( + Range range, DiscreteDomain domain) { + checkNotNull(range); + checkNotNull(domain); + Range effectiveRange = range; + try { + if (!range.hasLowerBound()) { + effectiveRange = effectiveRange.intersection(Ranges.atLeast(domain.minValue())); + } + if (!range.hasUpperBound()) { + effectiveRange = effectiveRange.intersection(Ranges.atMost(domain.maxValue())); + } + } catch (NoSuchElementException e) { + throw new IllegalArgumentException(e); + } + + // Per class spec, we are allowed to throw CCE if necessary + boolean empty = effectiveRange.isEmpty() + || Range.compareOrThrow( + range.lowerBound.leastValueAbove(domain), + range.upperBound.greatestValueBelow(domain)) > 0; + + return empty + ? new EmptyContiguousSet(domain) + : new RegularContiguousSet(effectiveRange, domain); + } + + final DiscreteDomain domain; + + ContiguousSet(DiscreteDomain domain) { + super(Ordering.natural()); + this.domain = domain; + } + + @Override public ContiguousSet headSet(C toElement) { + return headSetImpl(checkNotNull(toElement), false); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override public ContiguousSet headSet(C toElement, boolean inclusive) { + return headSetImpl(checkNotNull(toElement), inclusive); + } + + @Override public ContiguousSet subSet(C fromElement, C toElement) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator().compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, true, toElement, false); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override public ContiguousSet subSet(C fromElement, boolean fromInclusive, C toElement, + boolean toInclusive) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator().compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, fromInclusive, toElement, toInclusive); + } + + @Override public ContiguousSet tailSet(C fromElement) { + return tailSetImpl(checkNotNull(fromElement), true); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override public ContiguousSet tailSet(C fromElement, boolean inclusive) { + return tailSetImpl(checkNotNull(fromElement), inclusive); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides parameter validation. + */ + /*@Override*/ abstract ContiguousSet headSetImpl(C toElement, boolean inclusive); + + /*@Override*/ abstract ContiguousSet subSetImpl(C fromElement, boolean fromInclusive, + C toElement, boolean toInclusive); + + /*@Override*/ abstract ContiguousSet tailSetImpl(C fromElement, boolean inclusive); + + /** + * Returns the set of values that are contained in both this set and the other. + * + *

This method should always be used instead of + * {@link Sets#intersection} for {@link ContiguousSet} instances. + */ + public abstract ContiguousSet intersection(ContiguousSet other); + + /** + * Returns a range, closed on both ends, whose endpoints are the minimum and maximum values + * contained in this set. This is equivalent to {@code range(CLOSED, CLOSED)}. + * + * @throws NoSuchElementException if this set is empty + */ + public abstract Range range(); + + /** + * Returns the minimal range with the given boundary types for which all values in this set are + * {@linkplain Range#contains(Comparable) contained} within the range. + * + *

Note that this method will return ranges with unbounded endpoints if {@link BoundType#OPEN} + * is requested for a domain minimum or maximum. For example, if {@code set} was created from the + * range {@code [1..Integer.MAX_VALUE]} then {@code set.range(CLOSED, OPEN)} must return + * {@code [1..∞)}. + * + * @throws NoSuchElementException if this set is empty + */ + public abstract Range range(BoundType lowerBoundType, BoundType upperBoundType); + + /** Returns a short-hand representation of the contents such as {@code "[1..100]"}. */ + @Override public String toString() { + return range().toString(); + } +} diff --git a/guava/src/com/google/common/collect/Count.java b/guava/src/com/google/common/collect/Count.java new file mode 100644 index 0000000..768e298 --- /dev/null +++ b/guava/src/com/google/common/collect/Count.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * A mutable value of type {@code int}, for multisets to use in tracking counts of values. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class Count implements Serializable { + private int value; + + Count(int value) { + this.value = value; + } + + public int get() { + return value; + } + + public int getAndAdd(int delta) { + int result = value; + value = result + delta; + return result; + } + + public int addAndGet(int delta) { + return value += delta; + } + + public void set(int newValue) { + value = newValue; + } + + public int getAndSet(int newValue) { + int result = value; + value = newValue; + return result; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof Count && ((Count) obj).value == value; + } + + @Override + public String toString() { + return Integer.toString(value); + } +} diff --git a/guava/src/com/google/common/collect/Cut.java b/guava/src/com/google/common/collect/Cut.java new file mode 100644 index 0000000..204ea0c --- /dev/null +++ b/guava/src/com/google/common/collect/Cut.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Booleans; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * Implementation detail for the internal structure of {@link Range} instances. Represents + * a unique way of "cutting" a "number line" (actually of instances of type {@code C}, not + * necessarily "numbers") into two sections; this can be done below a certain value, above + * a certain value, below all values or above all values. With this object defined in this + * way, an interval can always be represented by a pair of {@code Cut} instances. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +abstract class Cut implements Comparable>, Serializable { + final C endpoint; + + Cut(@Nullable C endpoint) { + this.endpoint = endpoint; + } + + abstract boolean isLessThan(C value); + + abstract BoundType typeAsLowerBound(); + abstract BoundType typeAsUpperBound(); + + abstract Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain); + abstract Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain); + + abstract void describeAsLowerBound(StringBuilder sb); + abstract void describeAsUpperBound(StringBuilder sb); + + abstract C leastValueAbove(DiscreteDomain domain); + abstract C greatestValueBelow(DiscreteDomain domain); + + /* + * The canonical form is a BelowValue cut whenever possible, otherwise ABOVE_ALL, or + * (only in the case of types that are unbounded below) BELOW_ALL. + */ + Cut canonical(DiscreteDomain domain) { + return this; + } + + // note: overriden by {BELOW,ABOVE}_ALL + @Override + public int compareTo(Cut that) { + if (that == belowAll()) { + return 1; + } + if (that == aboveAll()) { + return -1; + } + int result = Range.compareOrThrow(endpoint, that.endpoint); + if (result != 0) { + return result; + } + // same value. below comes before above + return Booleans.compare( + this instanceof AboveValue, that instanceof AboveValue); + } + + C endpoint() { + return endpoint; + } + + @SuppressWarnings("unchecked") // catching CCE + @Override public boolean equals(Object obj) { + if (obj instanceof Cut) { + // It might not really be a Cut, but we'll catch a CCE if it's not + Cut that = (Cut) obj; + try { + int compareResult = compareTo(that); + return compareResult == 0; + } catch (ClassCastException ignored) { + } + } + return false; + } + + /* + * The implementation neither produces nor consumes any non-null instance of type C, so + * casting the type parameter is safe. + */ + @SuppressWarnings("unchecked") + static Cut belowAll() { + return (Cut) BelowAll.INSTANCE; + } + + private static final long serialVersionUID = 0; + + private static final class BelowAll extends Cut> { + private static final BelowAll INSTANCE = new BelowAll(); + + private BelowAll() { + super(null); + } + @Override Comparable endpoint() { + throw new IllegalStateException("range unbounded on this side"); + } + @Override boolean isLessThan(Comparable value) { + return true; + } + @Override BoundType typeAsLowerBound() { + throw new IllegalStateException(); + } + @Override BoundType typeAsUpperBound() { + throw new AssertionError("this statement should be unreachable"); + } + @Override Cut> withLowerBoundType(BoundType boundType, + DiscreteDomain> domain) { + throw new IllegalStateException(); + } + @Override Cut> withUpperBoundType(BoundType boundType, + DiscreteDomain> domain) { + throw new AssertionError("this statement should be unreachable"); + } + @Override void describeAsLowerBound(StringBuilder sb) { + sb.append("(-\u221e"); + } + @Override void describeAsUpperBound(StringBuilder sb) { + throw new AssertionError(); + } + @Override Comparable leastValueAbove( + DiscreteDomain> domain) { + return domain.minValue(); + } + @Override Comparable greatestValueBelow( + DiscreteDomain> domain) { + throw new AssertionError(); + } + @Override Cut> canonical( + DiscreteDomain> domain) { + try { + return Cut.>belowValue(domain.minValue()); + } catch (NoSuchElementException e) { + return this; + } + } + @Override public int compareTo(Cut> o) { + return (o == this) ? 0 : -1; + } + private Object readResolve() { + return INSTANCE; + } + private static final long serialVersionUID = 0; + } + + /* + * The implementation neither produces nor consumes any non-null instance of + * type C, so casting the type parameter is safe. + */ + @SuppressWarnings("unchecked") + static Cut aboveAll() { + return (Cut) AboveAll.INSTANCE; + } + + private static final class AboveAll extends Cut> { + private static final AboveAll INSTANCE = new AboveAll(); + + private AboveAll() { + super(null); + } + @Override Comparable endpoint() { + throw new IllegalStateException("range unbounded on this side"); + } + @Override boolean isLessThan(Comparable value) { + return false; + } + @Override BoundType typeAsLowerBound() { + throw new AssertionError("this statement should be unreachable"); + } + @Override BoundType typeAsUpperBound() { + throw new IllegalStateException(); + } + @Override Cut> withLowerBoundType(BoundType boundType, + DiscreteDomain> domain) { + throw new AssertionError("this statement should be unreachable"); + } + @Override Cut> withUpperBoundType(BoundType boundType, + DiscreteDomain> domain) { + throw new IllegalStateException(); + } + @Override void describeAsLowerBound(StringBuilder sb) { + throw new AssertionError(); + } + @Override void describeAsUpperBound(StringBuilder sb) { + sb.append("+\u221e)"); + } + @Override Comparable leastValueAbove( + DiscreteDomain> domain) { + throw new AssertionError(); + } + @Override Comparable greatestValueBelow( + DiscreteDomain> domain) { + return domain.maxValue(); + } + @Override public int compareTo(Cut> o) { + return (o == this) ? 0 : 1; + } + private Object readResolve() { + return INSTANCE; + } + private static final long serialVersionUID = 0; + } + + static Cut belowValue(C endpoint) { + return new BelowValue(endpoint); + } + + private static final class BelowValue extends Cut { + BelowValue(C endpoint) { + super(checkNotNull(endpoint)); + } + + @Override boolean isLessThan(C value) { + return Range.compareOrThrow(endpoint, value) <= 0; + } + @Override BoundType typeAsLowerBound() { + return BoundType.CLOSED; + } + @Override BoundType typeAsUpperBound() { + return BoundType.OPEN; + } + @Override Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case CLOSED: + return this; + case OPEN: + @Nullable C previous = domain.previous(endpoint); + return (previous == null) ? Cut.belowAll() : new AboveValue(previous); + default: + throw new AssertionError(); + } + } + @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case CLOSED: + @Nullable C previous = domain.previous(endpoint); + return (previous == null) ? Cut.aboveAll() : new AboveValue(previous); + case OPEN: + return this; + default: + throw new AssertionError(); + } + } + @Override void describeAsLowerBound(StringBuilder sb) { + sb.append('[').append(endpoint); + } + @Override void describeAsUpperBound(StringBuilder sb) { + sb.append(endpoint).append(')'); + } + @Override C leastValueAbove(DiscreteDomain domain) { + return endpoint; + } + @Override C greatestValueBelow(DiscreteDomain domain) { + return domain.previous(endpoint); + } + @Override public int hashCode() { + return endpoint.hashCode(); + } + private static final long serialVersionUID = 0; + } + + static Cut aboveValue(C endpoint) { + return new AboveValue(endpoint); + } + + private static final class AboveValue extends Cut { + AboveValue(C endpoint) { + super(checkNotNull(endpoint)); + } + + @Override boolean isLessThan(C value) { + return Range.compareOrThrow(endpoint, value) < 0; + } + @Override BoundType typeAsLowerBound() { + return BoundType.OPEN; + } + @Override BoundType typeAsUpperBound() { + return BoundType.CLOSED; + } + @Override Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case OPEN: + return this; + case CLOSED: + @Nullable C next = domain.next(endpoint); + return (next == null) ? Cut.belowAll() : belowValue(next); + default: + throw new AssertionError(); + } + } + @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case OPEN: + @Nullable C next = domain.next(endpoint); + return (next == null) ? Cut.aboveAll() : belowValue(next); + case CLOSED: + return this; + default: + throw new AssertionError(); + } + } + @Override void describeAsLowerBound(StringBuilder sb) { + sb.append('(').append(endpoint); + } + @Override void describeAsUpperBound(StringBuilder sb) { + sb.append(endpoint).append(']'); + } + @Override C leastValueAbove(DiscreteDomain domain) { + return domain.next(endpoint); + } + @Override C greatestValueBelow(DiscreteDomain domain) { + return endpoint; + } + @Override Cut canonical(DiscreteDomain domain) { + C next = leastValueAbove(domain); + return (next != null) ? belowValue(next) : Cut.aboveAll(); + } + @Override public int hashCode() { + return ~endpoint.hashCode(); + } + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java b/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java new file mode 100644 index 0000000..d2d0088 --- /dev/null +++ b/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import javax.annotation.Nullable; + +/** + * A descending wrapper around an {@code ImmutableSortedMultiset} + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +final class DescendingImmutableSortedMultiset extends ImmutableSortedMultiset { + private final transient ImmutableSortedMultiset forward; + + DescendingImmutableSortedMultiset(ImmutableSortedMultiset forward) { + this.forward = forward; + } + + @Override + public int count(@Nullable Object element) { + return forward.count(element); + } + + @Override + public Entry firstEntry() { + return forward.lastEntry(); + } + + @Override + public Entry lastEntry() { + return forward.firstEntry(); + } + + @Override + public int size() { + return forward.size(); + } + + @Override + public ImmutableSortedSet elementSet() { + return forward.elementSet().descendingSet(); + } + + @Override + ImmutableSet> createEntrySet() { + final ImmutableSet> forwardEntrySet = forward.entrySet(); + return new EntrySet() { + @Override + public int size() { + return forwardEntrySet.size(); + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + return forwardEntrySet.asList().reverse(); + } + }; + } + + @Override + public ImmutableSortedMultiset descendingMultiset() { + return forward; + } + + @Override + public ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType) { + return forward.tailMultiset(upperBound, boundType).descendingMultiset(); + } + + @Override + public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return forward.headMultiset(lowerBound, boundType).descendingMultiset(); + } + + @Override + boolean isPartialView() { + return forward.isPartialView(); + } +} diff --git a/guava/src/com/google/common/collect/DiscreteDomain.java b/guava/src/com/google/common/collect/DiscreteDomain.java new file mode 100644 index 0000000..a3f97f3 --- /dev/null +++ b/guava/src/com/google/common/collect/DiscreteDomain.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; + +/** + * A descriptor for a discrete {@code Comparable} domain such as all + * {@link Integer}s. A discrete domain is one that supports the three basic + * operations: {@link #next}, {@link #previous} and {@link #distance}, according + * to their specifications. The methods {@link #minValue} and {@link #maxValue} + * should also be overridden for bounded types. + * + *

A discrete domain always represents the entire set of values of its + * type; it cannot represent partial domains such as "prime integers" or + * "strings of length 5." + * + *

See the Guava User Guide section on + * {@code DiscreteDomain}. + * + * @author Kevin Bourrillion + * @since 10.0 + * @see DiscreteDomains + */ +@GwtCompatible +@Beta +public abstract class DiscreteDomain { + /** Constructor for use by subclasses. */ + protected DiscreteDomain() {} + + /** + * Returns the unique least value of type {@code C} that is greater than + * {@code value}, or {@code null} if none exists. Inverse operation to {@link + * #previous}. + * + * @param value any value of type {@code C} + * @return the least value greater than {@code value}, or {@code null} if + * {@code value} is {@code maxValue()} + */ + public abstract C next(C value); + + /** + * Returns the unique greatest value of type {@code C} that is less than + * {@code value}, or {@code null} if none exists. Inverse operation to {@link + * #next}. + * + * @param value any value of type {@code C} + * @return the greatest value less than {@code value}, or {@code null} if + * {@code value} is {@code minValue()} + */ + public abstract C previous(C value); + + /** + * Returns a signed value indicating how many nested invocations of {@link + * #next} (if positive) or {@link #previous} (if negative) are needed to reach + * {@code end} starting from {@code start}. For example, if {@code end = + * next(next(next(start)))}, then {@code distance(start, end) == 3} and {@code + * distance(end, start) == -3}. As well, {@code distance(a, a)} is always + * zero. + * + *

Note that this function is necessarily well-defined for any discrete + * type. + * + * @return the distance as described above, or {@link Long#MIN_VALUE} or + * {@link Long#MAX_VALUE} if the distance is too small or too large, + * respectively. + */ + public abstract long distance(C start, C end); + + /** + * Returns the minimum value of type {@code C}, if it has one. The minimum + * value is the unique value for which {@link Comparable#compareTo(Object)} + * never returns a positive value for any input of type {@code C}. + * + *

The default implementation throws {@code NoSuchElementException}. + * + * @return the minimum value of type {@code C}; never null + * @throws NoSuchElementException if the type has no (practical) minimum + * value; for example, {@link java.math.BigInteger} + */ + public C minValue() { + throw new NoSuchElementException(); + } + + /** + * Returns the maximum value of type {@code C}, if it has one. The maximum + * value is the unique value for which {@link Comparable#compareTo(Object)} + * never returns a negative value for any input of type {@code C}. + * + *

The default implementation throws {@code NoSuchElementException}. + * + * @return the maximum value of type {@code C}; never null + * @throws NoSuchElementException if the type has no (practical) maximum + * value; for example, {@link java.math.BigInteger} + */ + public C maxValue() { + throw new NoSuchElementException(); + } +} diff --git a/guava/src/com/google/common/collect/DiscreteDomains.java b/guava/src/com/google/common/collect/DiscreteDomains.java new file mode 100644 index 0000000..4cd5b48 --- /dev/null +++ b/guava/src/com/google/common/collect/DiscreteDomains.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * Factories for common {@link DiscreteDomain} instances. + * + *

See the Guava User Guide section on + * {@code DiscreteDomain}. + * + * @author Gregory Kick + * @since 10.0 + */ +@GwtCompatible +@Beta +public final class DiscreteDomains { + private DiscreteDomains() {} + + /** + * Returns the discrete domain for values of type {@code Integer}. + */ + public static DiscreteDomain integers() { + return IntegerDomain.INSTANCE; + } + + private static final class IntegerDomain extends DiscreteDomain + implements Serializable { + private static final IntegerDomain INSTANCE = new IntegerDomain(); + + @Override public Integer next(Integer value) { + int i = value; + return (i == Integer.MAX_VALUE) ? null : i + 1; + } + + @Override public Integer previous(Integer value) { + int i = value; + return (i == Integer.MIN_VALUE) ? null : i - 1; + } + + @Override public long distance(Integer start, Integer end) { + return (long) end - start; + } + + @Override public Integer minValue() { + return Integer.MIN_VALUE; + } + + @Override public Integer maxValue() { + return Integer.MAX_VALUE; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the discrete domain for values of type {@code Long}. + */ + public static DiscreteDomain longs() { + return LongDomain.INSTANCE; + } + + private static final class LongDomain extends DiscreteDomain + implements Serializable { + private static final LongDomain INSTANCE = new LongDomain(); + + @Override public Long next(Long value) { + long l = value; + return (l == Long.MAX_VALUE) ? null : l + 1; + } + + @Override public Long previous(Long value) { + long l = value; + return (l == Long.MIN_VALUE) ? null : l - 1; + } + + @Override public long distance(Long start, Long end) { + long result = end - start; + if (end > start && result < 0) { // overflow + return Long.MAX_VALUE; + } + if (end < start && result > 0) { // underflow + return Long.MIN_VALUE; + } + return result; + } + + @Override public Long minValue() { + return Long.MIN_VALUE; + } + + @Override public Long maxValue() { + return Long.MAX_VALUE; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the discrete domain for values of type {@code BigInteger}. + */ + // TODO(kevinb): make sure it's tested, and make it public + static DiscreteDomain bigIntegers() { + return BigIntegerDomain.INSTANCE; + } + + private static final class BigIntegerDomain extends DiscreteDomain + implements Serializable { + private static final BigIntegerDomain INSTANCE = new BigIntegerDomain(); + + private static final BigInteger MIN_LONG = + BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger MAX_LONG = + BigInteger.valueOf(Long.MAX_VALUE); + + @Override public BigInteger next(BigInteger value) { + return value.add(BigInteger.ONE); + } + + @Override public BigInteger previous(BigInteger value) { + return value.subtract(BigInteger.ONE); + } + + @Override public long distance(BigInteger start, BigInteger end) { + return start.subtract(end).max(MIN_LONG).min(MAX_LONG).longValue(); + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/EmptyContiguousSet.java b/guava/src/com/google/common/collect/EmptyContiguousSet.java new file mode 100644 index 0000000..fbf1961 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyContiguousSet.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. +*/ +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty contiguous set. + * + * @author Gregory Kick + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("unchecked") // allow ungenerified Comparable types +final class EmptyContiguousSet extends ContiguousSet { + EmptyContiguousSet(DiscreteDomain domain) { + super(domain); + } + + @Override public C first() { + throw new NoSuchElementException(); + } + + @Override public C last() { + throw new NoSuchElementException(); + } + + @Override public int size() { + return 0; + } + + @Override public ContiguousSet intersection(ContiguousSet other) { + return this; + } + + @Override public Range range() { + throw new NoSuchElementException(); + } + + @Override public Range range(BoundType lowerBoundType, BoundType upperBoundType) { + throw new NoSuchElementException(); + } + + @Override ContiguousSet headSetImpl(C toElement, boolean inclusive) { + return this; + } + + @Override ContiguousSet subSetImpl( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { + return this; + } + + @Override ContiguousSet tailSetImpl(C fromElement, boolean fromInclusive) { + return this; + } + + @GwtIncompatible("not used by GWT emulation") + @Override int indexOf(Object target) { + return -1; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @Override boolean isPartialView() { + return false; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public ImmutableList asList() { + return ImmutableList.of(); + } + + @Override public String toString() { + return "[]"; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 0; + } + + @GwtIncompatible("serialization") + private static final class SerializedForm implements Serializable { + private final DiscreteDomain domain; + + private SerializedForm(DiscreteDomain domain) { + this.domain = domain; + } + + private Object readResolve() { + return new EmptyContiguousSet(domain); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("serialization") + @Override + Object writeReplace() { + return new SerializedForm(domain); + } + + @GwtIncompatible("NavigableSet") + ImmutableSortedSet createDescendingSet() { + return new EmptyImmutableSortedSet(Ordering.natural().reverse()); + } +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableBiMap.java b/guava/src/com/google/common/collect/EmptyImmutableBiMap.java new file mode 100644 index 0000000..8839a07 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableBiMap.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Bimap with no mappings. + * + * @author Jared Levy + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class EmptyImmutableBiMap extends ImmutableBiMap { + static final EmptyImmutableBiMap INSTANCE = new EmptyImmutableBiMap(); + + private EmptyImmutableBiMap() {} + + @Override ImmutableMap delegate() { + return ImmutableMap.of(); + } + @Override public ImmutableBiMap inverse() { + return this; + } + @Override boolean isPartialView() { + return false; + } + Object readResolve() { + return INSTANCE; // preserve singleton property + } +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableList.java b/guava/src/com/google/common/collect/EmptyImmutableList.java new file mode 100644 index 0000000..b854d2b --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableList.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * An empty immutable list. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +final class EmptyImmutableList extends ImmutableList { + static final EmptyImmutableList INSTANCE = new EmptyImmutableList(); + + private EmptyImmutableList() {} + + @Override + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override boolean isPartialView() { + return false; + } + + @Override public boolean contains(@Nullable Object target) { + return false; + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public UnmodifiableIterator iterator() { + return listIterator(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + @Override + public Object get(int index) { + // guaranteed to fail, but at least we get a consistent message + checkElementIndex(index, 0); + throw new AssertionError("unreachable"); + } + + @Override public int indexOf(@Nullable Object target) { + return -1; + } + + @Override public int lastIndexOf(@Nullable Object target) { + return -1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, 0); + return this; + } + + @Override public ImmutableList reverse() { + return this; + } + + @Override public UnmodifiableListIterator listIterator() { + return Iterators.EMPTY_LIST_ITERATOR; + } + + @Override public UnmodifiableListIterator listIterator(int start) { + checkPositionIndex(start, 0); + return Iterators.EMPTY_LIST_ITERATOR; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof List) { + List that = (List) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 1; + } + + @Override public String toString() { + return "[]"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java b/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java new file mode 100644 index 0000000..2a6836d --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +class EmptyImmutableListMultimap extends ImmutableListMultimap { + static final EmptyImmutableListMultimap INSTANCE + = new EmptyImmutableListMultimap(); + + private EmptyImmutableListMultimap() { + super(ImmutableMap.>of(), 0); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableMap.java b/guava/src/com/google/common/collect/EmptyImmutableMap.java new file mode 100644 index 0000000..1aaf1c9 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableMap.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An empty immutable map. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +final class EmptyImmutableMap extends ImmutableMap { + static final EmptyImmutableMap INSTANCE = new EmptyImmutableMap(); + + private EmptyImmutableMap() {} + + @Override public Object get(@Nullable Object key) { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean containsKey(@Nullable Object key) { + return false; + } + + @Override public boolean containsValue(@Nullable Object value) { + return false; + } + + @Override ImmutableSet> createEntrySet() { + throw new AssertionError("should never be called"); + } + + @Override public ImmutableSet> entrySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableSet keySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableCollection values() { + return ImmutableCollection.EMPTY_IMMUTABLE_COLLECTION; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Map) { + Map that = (Map) object; + return that.isEmpty(); + } + return false; + } + + @Override boolean isPartialView() { + return false; + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "{}"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableMultiset.java b/guava/src/com/google/common/collect/EmptyImmutableMultiset.java new file mode 100644 index 0000000..1931342 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableMultiset.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; + +import javax.annotation.Nullable; + +/** + * An empty immutable multiset. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true) +final class EmptyImmutableMultiset extends ImmutableMultiset { + static final EmptyImmutableMultiset INSTANCE = new EmptyImmutableMultiset(); + + @Override + public int count(@Nullable Object element) { + return 0; + } + + @Override + public boolean contains(@Nullable Object object) { + return false; + } + + @Override + public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof Multiset) { + Multiset other = (Multiset) object; + return other.isEmpty(); + } + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public ImmutableSet elementSet() { + return ImmutableSet.of(); + } + + @Override + public ImmutableSet> entrySet() { + return ImmutableSet.of(); + } + + @Override + ImmutableSet> createEntrySet() { + throw new AssertionError("should never be called"); + } + + @Override + public int size() { + return 0; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override + public T[] toArray(T[] other) { + return asList().toArray(other); + } + + @Override + public ImmutableList asList() { + return ImmutableList.of(); + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableSet.java b/guava/src/com/google/common/collect/EmptyImmutableSet.java new file mode 100644 index 0000000..e70b051 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableSet.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty immutable set. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +final class EmptyImmutableSet extends ImmutableSet { + static final EmptyImmutableSet INSTANCE = new EmptyImmutableSet(); + + private EmptyImmutableSet() {} + + @Override + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(@Nullable Object target) { + return false; + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @Override boolean isPartialView() { + return false; + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + return asList().toArray(a); + } + + @Override + public ImmutableList asList() { + return ImmutableList.of(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @Override public final int hashCode() { + return 0; + } + + @Override boolean isHashCodeFast() { + return true; + } + + @Override public String toString() { + return "[]"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java b/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java new file mode 100644 index 0000000..810de3c --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Mike Ward + */ +@GwtCompatible(serializable = true) +class EmptyImmutableSetMultimap extends ImmutableSetMultimap { + static final EmptyImmutableSetMultimap INSTANCE + = new EmptyImmutableSetMultimap(); + + private EmptyImmutableSetMultimap() { + super(ImmutableMap.>of(), 0, null); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableSortedMap.java b/guava/src/com/google/common/collect/EmptyImmutableSortedMap.java new file mode 100644 index 0000000..eafc126 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableSortedMap.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Comparator; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An empty immutable sorted map. + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +final class EmptyImmutableSortedMap extends ImmutableSortedMap { + private final transient ImmutableSortedSet keySet; + + EmptyImmutableSortedMap(Comparator comparator) { + this.keySet = ImmutableSortedSet.emptySet(comparator); + } + + EmptyImmutableSortedMap( + Comparator comparator, ImmutableSortedMap descendingMap) { + super(descendingMap); + this.keySet = ImmutableSortedSet.emptySet(comparator); + } + + @Override + public V get(@Nullable Object key) { + return null; + } + + @Override + public ImmutableSortedSet keySet() { + return keySet; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ImmutableCollection values() { + return ImmutableList.of(); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof Map) { + Map map = (Map) object; + return map.isEmpty(); + } + return false; + } + + @Override + public String toString() { + return "{}"; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public ImmutableSet> entrySet() { + return ImmutableSet.of(); + } + + @Override + ImmutableSet> createEntrySet() { + throw new AssertionError("should never be called"); + } + + @Override + public ImmutableSortedMap headMap(K toKey, boolean inclusive) { + checkNotNull(toKey); + return this; + } + + @Override + public ImmutableSortedMap tailMap(K fromKey, boolean inclusive) { + checkNotNull(fromKey); + return this; + } + + @Override + ImmutableSortedMap createDescendingMap() { + return new EmptyImmutableSortedMap(Ordering.from(comparator()).reverse(), this); + } +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableSortedMultiset.java b/guava/src/com/google/common/collect/EmptyImmutableSortedMultiset.java new file mode 100644 index 0000000..a7ddf28 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableSortedMultiset.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Comparator; + +import javax.annotation.Nullable; + +/** + * An empty immutable sorted multiset. + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // Uses writeReplace, not default serialization +final class EmptyImmutableSortedMultiset extends ImmutableSortedMultiset { + private final ImmutableSortedSet elementSet; + + EmptyImmutableSortedMultiset(Comparator comparator) { + this.elementSet = ImmutableSortedSet.emptySet(comparator); + } + + @Override + public Entry firstEntry() { + return null; + } + + @Override + public Entry lastEntry() { + return null; + } + + @Override + public int count(@Nullable Object element) { + return 0; + } + + @Override + public boolean contains(@Nullable Object object) { + return false; + } + + @Override + public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override + public int size() { + return 0; + } + + @Override + public ImmutableSortedSet elementSet() { + return elementSet; + } + + @Override + public ImmutableSet> entrySet() { + return ImmutableSet.of(); + } + + @Override + ImmutableSet> createEntrySet() { + throw new AssertionError("should never be called"); + } + + @Override + public ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType) { + checkNotNull(upperBound); + checkNotNull(boundType); + return this; + } + + @Override + public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + checkNotNull(lowerBound); + checkNotNull(boundType); + return this; + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof Multiset) { + Multiset other = (Multiset) object; + return other.isEmpty(); + } + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "[]"; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override + public T[] toArray(T[] other) { + return asList().toArray(other); + } + + @Override + public ImmutableList asList() { + return ImmutableList.of(); + } +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableSortedSet.java b/guava/src/com/google/common/collect/EmptyImmutableSortedSet.java new file mode 100644 index 0000000..06508e0 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableSortedSet.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty immutable sorted set. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class EmptyImmutableSortedSet extends ImmutableSortedSet { + EmptyImmutableSortedSet(Comparator comparator) { + super(comparator); + } + + @Override + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @Override boolean isPartialView() { + return false; + } + + @Override public ImmutableList asList() { + return ImmutableList.of(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + return asList().toArray(a); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "[]"; + } + + @Override + public E first() { + throw new NoSuchElementException(); + } + + @Override + public E last() { + throw new NoSuchElementException(); + } + + @Override + ImmutableSortedSet headSetImpl(E toElement, boolean inclusive) { + return this; + } + + @Override + ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return this; + } + + @Override + ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive) { + return this; + } + + @Override int indexOf(@Nullable Object target) { + return -1; + } + + @Override + ImmutableSortedSet createDescendingSet() { + return new EmptyImmutableSortedSet(Ordering.from(comparator).reverse()); + } +} diff --git a/guava/src/com/google/common/collect/EmptyImmutableTable.java b/guava/src/com/google/common/collect/EmptyImmutableTable.java new file mode 100644 index 0000000..65b8042 --- /dev/null +++ b/guava/src/com/google/common/collect/EmptyImmutableTable.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * An empty implementation of {@link ImmutableTable}. + * + * @author Gregory Kick + */ +@GwtCompatible +@Immutable +final class EmptyImmutableTable extends ImmutableTable { + static final EmptyImmutableTable INSTANCE = new EmptyImmutableTable(); + + private EmptyImmutableTable() {} + + @Override public int size() { + return 0; + } + + @Override public Object get(@Nullable Object rowKey, + @Nullable Object columnKey) { + return null; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + Table that = (Table) obj; + return that.isEmpty(); + } else { + return false; + } + } + + @Override public int hashCode() { + return 0; + } + + @Override public ImmutableSet> cellSet() { + return ImmutableSet.of(); + } + + @Override public ImmutableMap column(Object columnKey) { + checkNotNull(columnKey); + return ImmutableMap.of(); + } + + @Override public ImmutableSet columnKeySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableMap> columnMap() { + return ImmutableMap.of(); + } + + @Override public boolean contains(@Nullable Object rowKey, + @Nullable Object columnKey) { + return false; + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return false; + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return false; + } + + @Override public boolean containsValue(@Nullable Object value) { + return false; + } + + @Override public ImmutableMap row(Object rowKey) { + checkNotNull(rowKey); + return ImmutableMap.of(); + } + + @Override public ImmutableSet rowKeySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableMap> rowMap() { + return ImmutableMap.of(); + } + + @Override public String toString() { + return "{}"; + } + + @Override public ImmutableCollection values() { + return ImmutableSet.of(); + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EnumBiMap.java b/guava/src/com/google/common/collect/EnumBiMap.java new file mode 100644 index 0000000..05d84ed --- /dev/null +++ b/guava/src/com/google/common/collect/EnumBiMap.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Map; + +/** + * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values + * are not permitted. An {@code EnumBiMap} and its inverse are both + * serializable. + * + *

See the Guava User Guide article on + * {@code BiMap}. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class EnumBiMap, V extends Enum> + extends AbstractBiMap { + private transient Class keyType; + private transient Class valueType; + + /** + * Returns a new, empty {@code EnumBiMap} using the specified key and value + * types. + * + * @param keyType the key type + * @param valueType the value type + */ + public static , V extends Enum> EnumBiMap + create(Class keyType, Class valueType) { + return new EnumBiMap(keyType, valueType); + } + + /** + * Returns a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumBiMap}, the new bimap has the same types as + * the provided map. Otherwise, the specified map must contain at least one + * mapping, in order to determine the key and value types. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} + * instance and contains no mappings + */ + public static , V extends Enum> EnumBiMap + create(Map map) { + EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumBiMap(Class keyType, Class valueType) { + super(WellBehavedMap.wrap(new EnumMap(keyType)), + WellBehavedMap.wrap(new EnumMap(valueType))); + this.keyType = keyType; + this.valueType = valueType; + } + + static > Class inferKeyType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).keyType(); + } + if (map instanceof EnumHashBiMap) { + return ((EnumHashBiMap) map).keyType(); + } + checkArgument(!map.isEmpty()); + return map.keySet().iterator().next().getDeclaringClass(); + } + + private static > Class inferValueType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).valueType; + } + checkArgument(!map.isEmpty()); + return map.values().iterator().next().getDeclaringClass(); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** Returns the associated value type. */ + public Class valueType() { + return valueType; + } + + @Override + K checkKey(K key) { + return checkNotNull(key); + } + + @Override + V checkValue(V value) { + return checkNotNull(value); + } + + /** + * @serialData the key class, value class, number of entries, first key, first + * value, second key, second value, and so on. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + stream.writeObject(valueType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading fields populated by writeObject + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + valueType = (Class) stream.readObject(); + setDelegates( + WellBehavedMap.wrap(new EnumMap(keyType)), + WellBehavedMap.wrap(new EnumMap(valueType))); + Serialization.populateMap(this, stream); + } + + @GwtIncompatible("not needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EnumHashBiMap.java b/guava/src/com/google/common/collect/EnumHashBiMap.java new file mode 100644 index 0000000..c43daf0 --- /dev/null +++ b/guava/src/com/google/common/collect/EnumHashBiMap.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and + * a {@code HashMap} instance for values-to-keys. Null keys are not permitted, + * but null values are. An {@code EnumHashBiMap} and its inverse are both + * serializable. + * + *

See the Guava User Guide article on + * {@code BiMap}. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class EnumHashBiMap, V> + extends AbstractBiMap { + private transient Class keyType; + + /** + * Returns a new, empty {@code EnumHashBiMap} using the specified key type. + * + * @param keyType the key type + */ + public static , V> EnumHashBiMap + create(Class keyType) { + return new EnumHashBiMap(keyType); + } + + /** + * Constructs a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumHashBiMap} or an {@link EnumBiMap}, the new + * bimap has the same key type as the input bimap. Otherwise, the specified + * map must contain at least one mapping, in order to determine the key type. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an + * {@code EnumHashBiMap} instance and contains no mappings + */ + public static , V> EnumHashBiMap + create(Map map) { + EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumHashBiMap(Class keyType) { + super(WellBehavedMap.wrap( + new EnumMap(keyType)), + Maps.newHashMapWithExpectedSize( + keyType.getEnumConstants().length)); + this.keyType = keyType; + } + + // Overriding these 3 methods to show that values may be null (but not keys) + + @Override + K checkKey(K key) { + return checkNotNull(key); + } + + @Override public V put(K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** + * @serialData the key class, number of entries, first key, first value, + * second key, second value, and so on. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading field populated by writeObject + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + setDelegates(WellBehavedMap.wrap(new EnumMap(keyType)), + new HashMap(keyType.getEnumConstants().length * 3 / 2)); + Serialization.populateMap(this, stream); + } + + @GwtIncompatible("only needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/EnumMultiset.java b/guava/src/com/google/common/collect/EnumMultiset.java new file mode 100644 index 0000000..756f01e --- /dev/null +++ b/guava/src/com/google/common/collect/EnumMultiset.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Iterator; + +/** + * Multiset implementation backed by an {@link EnumMap}. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class EnumMultiset> extends AbstractMapBasedMultiset { + /** Creates an empty {@code EnumMultiset}. */ + public static > EnumMultiset create(Class type) { + return new EnumMultiset(type); + } + + /** + * Creates a new {@code EnumMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link + * Multiset}. + * + * @param elements the elements that the multiset should contain + * @throws IllegalArgumentException if {@code elements} is empty + */ + public static > EnumMultiset create(Iterable elements) { + Iterator iterator = elements.iterator(); + checkArgument(iterator.hasNext(), "EnumMultiset constructor passed empty Iterable"); + EnumMultiset multiset = new EnumMultiset(iterator.next().getDeclaringClass()); + Iterables.addAll(multiset, elements); + return multiset; + } + + private transient Class type; + + /** Creates an empty {@code EnumMultiset}. */ + private EnumMultiset(Class type) { + super(WellBehavedMap.wrap(new EnumMap(type))); + this.type = type; + } + + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(type); + Serialization.writeMultiset(this, stream); + } + + /** + * @serialData the {@code Class} for the enum type, the number of distinct + * elements, the first element, its count, the second element, its + * count, and so on + */ + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + Class localType = (Class) stream.readObject(); + type = localType; + setBackingMap(WellBehavedMap.wrap(new EnumMap(type))); + Serialization.populateMultiset(this, stream); + } + + @GwtIncompatible("Not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ExplicitOrdering.java b/guava/src/com/google/common/collect/ExplicitOrdering.java new file mode 100644 index 0000000..0a2e181 --- /dev/null +++ b/guava/src/com/google/common/collect/ExplicitOrdering.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.List; + +import javax.annotation.Nullable; + +/** An ordering that compares objects according to a given order. */ +@GwtCompatible(serializable = true) +final class ExplicitOrdering extends Ordering implements Serializable { + final ImmutableMap rankMap; + + ExplicitOrdering(List valuesInOrder) { + this(buildRankMap(valuesInOrder)); + } + + ExplicitOrdering(ImmutableMap rankMap) { + this.rankMap = rankMap; + } + + @Override public int compare(T left, T right) { + return rank(left) - rank(right); // safe because both are nonnegative + } + + private int rank(T value) { + Integer rank = rankMap.get(value); + if (rank == null) { + throw new IncomparableValueException(value); + } + return rank; + } + + private static ImmutableMap buildRankMap( + List valuesInOrder) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + int rank = 0; + for (T value : valuesInOrder) { + builder.put(value, rank++); + } + return builder.build(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof ExplicitOrdering) { + ExplicitOrdering that = (ExplicitOrdering) object; + return this.rankMap.equals(that.rankMap); + } + return false; + } + + @Override public int hashCode() { + return rankMap.hashCode(); + } + + @Override public String toString() { + return "Ordering.explicit(" + rankMap.keySet() + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/FluentIterable.java b/guava/src/com/google/common/collect/FluentIterable.java new file mode 100644 index 0000000..5482536 --- /dev/null +++ b/guava/src/com/google/common/collect/FluentIterable.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * {@code FluentIterable} provides a rich interface for manipulating {@code Iterable}s in a chained + * fashion. A {@code FluentIterable} can be created from an {@code Iterable}, or from a set of + * elements. The following types of methods are provided on {@code FluentIterable}: + *

    + *
  • chained methods which return a new {@code FluentIterable} based in some way on the contents + * of the current one (for example {@link #transform}) + *
  • conversion methods which copy the {@code FluentIterable}'s contents into a new collection or + * array (for example {@link #toImmutableList}) + *
  • element extraction methods which facilitate the retrieval of certain elements (for example + * {@link #last}) + *
  • query methods which answer questions about the {@code FluentIterable}'s contents (for example + * {@link #anyMatch}) + *
+ * + *

Here is an example that merges the lists returned by two separate database calls, transforms + * it by invoking {@code toString()} on each element, and returns the first 10 elements as an + * {@code ImmutableList}:

   {@code
+ *
+ *   FluentIterable
+ *       .from(database.getClientList())
+ *       .filter(activeInLastMonth())
+ *       .transform(Functions.toStringFunction())
+ *       .limit(10)
+ *       .toImmutableList();}
+ * + * Anything which can be done using {@code FluentIterable} could be done in a different fashion + * (often with {@link Iterables}), however the use of {@code FluentIterable} makes many sets of + * operations significantly more concise. + * + * @author Marcin Mikosik + * @since 12.0 + */ +@Beta +@GwtCompatible(emulated = true) +public abstract class FluentIterable implements Iterable { + // We store 'iterable' and use it instead of 'this' to allow Iterables to perform instanceof + // checks on the _original_ iterable when FluentIterable.from is used. + private final Iterable iterable; + + /** Constructor for use by subclasses. */ + protected FluentIterable() { + this.iterable = this; + } + + FluentIterable(Iterable iterable) { + this.iterable = checkNotNull(iterable); + } + + /** + * Returns a fluent iterable that wraps {@code iterable}, or {@code iterable} itself if it + * is already a {@code FluentIterable}. + */ + public static FluentIterable from(final Iterable iterable) { + return (iterable instanceof FluentIterable) ? (FluentIterable) iterable + : new FluentIterable(iterable) { + @Override + public Iterator iterator() { + return iterable.iterator(); + } + }; + } + + /** + * Construct a fluent iterable from another fluent iterable. This is obviously never necessary, + * but is intended to help call out cases where one migration from {@code Iterable} to + * {@code FluentIterable} has obviated the need to explicitly convert to a {@code FluentIterable}. + * + * @deprecated instances of {@code FluentIterable} don't need to be converted to + * {@code FluentIterable} + */ + @Deprecated + public static FluentIterable from(FluentIterable iterable) { + return checkNotNull(iterable); + } + + /** + * Returns a string representation of this fluent iterable, with the format + * {@code [e1, e2, ..., en]}. + */ + @Override + public String toString() { + return Iterables.toString(iterable); + } + + /** + * Returns the number of elements in this fluent iterable. + */ + public final int size() { + return Iterables.size(iterable); + } + + /** + * Returns {@code true} if this fluent iterable contains any object for which + * {@code equals(element)} is true. + */ + public final boolean contains(@Nullable Object element) { + return Iterables.contains(iterable, element); + } + + /** + * Returns a fluent iterable whose {@code Iterator} cycles indefinitely over the elements of + * this fluent iterable. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} does. After + * {@code remove()} is called, subsequent cycles omit the removed element, which is no longer in + * this fluent iterable. The iterator's {@code hasNext()} method returns {@code true} until + * this fluent iterable is empty. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + */ + public final FluentIterable cycle() { + return from(Iterables.cycle(iterable)); + } + + /** + * Returns the elements from this fluent iterable that satisfy a predicate. The + * resulting fluent iterable's iterator does not support {@code remove()}. + */ + public final FluentIterable filter(Predicate predicate) { + return from(Iterables.filter(iterable, predicate)); + } + + /** + * Returns the elements from this fluent iterable that are instances of class {@code type}. + * + * @param type the type of elements desired + */ + @GwtIncompatible("Class.isInstance") + public final FluentIterable filter(Class type) { + return from(Iterables.filter(iterable, type)); + } + + /** + * Returns {@code true} if any element in this fluent iterable satisfies the predicate. + */ + public final boolean anyMatch(Predicate predicate) { + return Iterables.any(iterable, predicate); + } + + /** + * Returns {@code true} if every element in this fluent iterable satisfies the predicate. + * If this fluent iterable is empty, {@code true} is returned. + */ + public final boolean allMatch(Predicate predicate) { + return Iterables.all(iterable, predicate); + } + + /** + * Returns an {@link Optional} containing the first element in this fluent iterable that + * satisfies the given predicate, if such an element exists. + * + *

Warning: avoid using a {@code predicate} that matches {@code null}. If {@code null} + * is matched in this fluent iterable, a {@link NullPointerException} will be thrown. + */ + public final Optional firstMatch(Predicate predicate) { + return Iterables.tryFind(iterable, predicate); + } + + /** + * Returns a fluent iterable that applies {@code function} to each element of this + * fluent iterable. + * + *

The returned fluent iterable's iterator supports {@code remove()} if this iterable's + * iterator does. After a successful {@code remove()} call, this fluent iterable no longer + * contains the corresponding element. + */ + public final FluentIterable transform(Function function) { + return from(Iterables.transform(iterable, function)); + } + + /** + * Applies {@code function} to each element of this fluent iterable and returns + * a fluent iterable with the concatenated combination of results. {@code function} + * returns an Iterable of results. + * + *

The returned fluent iterable's iterator supports {@code remove()} if this + * function-returned iterables' iterator does. After a successful {@code remove()} call, + * the returned fluent iterable no longer contains the corresponding element. + * + * @since 13.0 + */ + public FluentIterable transformAndConcat( + Function> function) { + return from(Iterables.concat(transform(function))); + } + + /** + * Returns an {@link Optional} containing the first element in this fluent iterable. + * If the iterable is empty, {@code Optional.absent()} is returned. + * + * @throws NullPointerException if the first element is null; if this is a possibility, use + * {@code iterator().next()} or {@link Iterables#getFirst} instead. + */ + public final Optional first() { + Iterator iterator = iterable.iterator(); + return iterator.hasNext() + ? Optional.of(iterator.next()) + : Optional.absent(); + } + + /** + * Returns an {@link Optional} containing the last element in this fluent iterable. + * If the iterable is empty, {@code Optional.absent()} is returned. + * + * @throws NullPointerException if the last element is null; if this is a possibility, use + * {@link Iterables#getLast} instead. + */ + public final Optional last() { + // Iterables#getLast was inlined here so we don't have to throw/catch a NSEE + + // TODO(kevinb): Support a concurrently modified collection? + if (iterable instanceof List) { + List list = (List) iterable; + if (list.isEmpty()) { + return Optional.absent(); + } + return Optional.of(list.get(list.size() - 1)); + } + Iterator iterator = iterable.iterator(); + if (!iterator.hasNext()) { + return Optional.absent(); + } + + /* + * TODO(kevinb): consider whether this "optimization" is worthwhile. Users + * with SortedSets tend to know they are SortedSets and probably would not + * call this method. + */ + if (iterable instanceof SortedSet) { + SortedSet sortedSet = (SortedSet) iterable; + return Optional.of(sortedSet.last()); + } + + while (true) { + E current = iterator.next(); + if (!iterator.hasNext()) { + return Optional.of(current); + } + } + } + + /** + * Returns a view of this fluent iterable that skips its first {@code numberToSkip} + * elements. If this fluent iterable contains fewer than {@code numberToSkip} elements, + * the returned fluent iterable skips all of its elements. + * + *

Modifications to this fluent iterable before a call to {@code iterator()} are + * reflected in the returned fluent iterable. That is, the its iterator skips the first + * {@code numberToSkip} elements that exist when the iterator is created, not when {@code skip()} + * is called. + * + *

The returned fluent iterable's iterator supports {@code remove()} if the + * {@code Iterator} of this fluent iterable supports it. Note that it is not + * possible to delete the last skipped element by immediately calling {@code remove()} on the + * returned fluent iterable's iterator, as the {@code Iterator} contract states that a call + * to {@code * remove()} before a call to {@code next()} will throw an + * {@link IllegalStateException}. + */ + public final FluentIterable skip(int numberToSkip) { + return from(Iterables.skip(iterable, numberToSkip)); + } + + /** + * Creates a fluent iterable with the first {@code size} elements of this + * fluent iterable. If this fluent iterable does not contain that many elements, + * the returned fluent iterable will have the same behavior as this fluent iterable. + * The returned fluent iterable's iterator supports {@code remove()} if this + * fluent iterable's iterator does. + * + * @param size the maximum number of elements in the returned fluent iterable + * @throws IllegalArgumentException if {@code size} is negative + */ + public final FluentIterable limit(int size) { + return from(Iterables.limit(iterable, size)); + } + + /** + * Determines whether this fluent iterable is empty. + */ + public final boolean isEmpty() { + return !iterable.iterator().hasNext(); + } + + /** + * Returns an {@code ImmutableList} containing all of the elements from this + * fluent iterable in proper sequence. + */ + public final ImmutableList toImmutableList() { + return ImmutableList.copyOf(iterable); + } + + /** + * Returns an {@code ImmutableList} containing all of the elements from this + * {@code FluentIterable} in the order specified by {@code comparator}. To produce an + * {@code ImmutableList} sorted by its natural ordering, use + * {@code toSortedImmutableList(Ordering.natural())}. + * + * @param comparator the function by which to sort list elements + * @throws NullPointerException if any element is null + * @since 13.0 + */ + public final ImmutableList toSortedImmutableList(Comparator comparator) { + return Ordering.from(comparator).immutableSortedCopy(iterable); + } + + /** + * Returns an {@code ImmutableSet} containing all of the elements from this + * fluent iterable with duplicates removed. + */ + public final ImmutableSet toImmutableSet() { + return ImmutableSet.copyOf(iterable); + } + + /** + * Returns an {@code ImmutableSortedSet} containing all of the elements from this + * {@code FluentIterable} in the order specified by {@code comparator}, with duplicates + * (determined by {@code comaprator.compare(x, y) == 0}) removed. To produce an + * {@code ImmutableSortedSet} sorted by its natural ordering, use + * {@code toImmutableSortedSet(Ordering.natural())}. + * + * @param comparator the function by which to sort set elements + * @throws NullPointerException if any element is null + */ + public final ImmutableSortedSet toImmutableSortedSet(Comparator comparator) { + return ImmutableSortedSet.copyOf(comparator, iterable); + } + + /** + * Returns an array containing all of the elements from this fluent iterable in iteration order. + * + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of this fluent iterable have + * been copied + */ + @GwtIncompatible("Array.newArray(Class, int)") + public final E[] toArray(Class type) { + return Iterables.toArray(iterable, type); + } + + /** + * Returns the element at the specified position in this fluent iterable. + * + * @param position position of the element to return + * @return the element at the specified position in this fluent iterable + * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to + * the size of this fluent iterable + */ + public final E get(int position) { + return Iterables.get(iterable, position); + } + + /** + * Function that transforms {@code Iterable} into a fluent iterable. + */ + private static class FromIterableFunction + implements Function, FluentIterable> { + @Override + public FluentIterable apply(Iterable fromObject) { + return FluentIterable.from(fromObject); + } + } +} diff --git a/guava/src/com/google/common/collect/ForwardingCollection.java b/guava/src/com/google/common/collect/ForwardingCollection.java new file mode 100644 index 0000000..a6a46f0 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingCollection.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; + +import javax.annotation.Nullable; + +/** + * A collection which forwards all its method calls to another collection. + * Subclasses should override one or more methods to modify the behavior of the + * backing collection as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingCollection} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardAddAll} method. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even + * when all of the methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingCollection extends ForwardingObject + implements Collection { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingCollection() {} + + @Override protected abstract Collection delegate(); + + @Override + public Iterator iterator() { + return delegate().iterator(); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override + public boolean removeAll(Collection collection) { + return delegate().removeAll(collection); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public boolean contains(Object object) { + return delegate().contains(object); + } + + @Override + public boolean add(E element) { + return delegate().add(element); + } + + @Override + public boolean remove(Object object) { + return delegate().remove(object); + } + + @Override + public boolean containsAll(Collection collection) { + return delegate().containsAll(collection); + } + + @Override + public boolean addAll(Collection collection) { + return delegate().addAll(collection); + } + + @Override + public boolean retainAll(Collection collection) { + return delegate().retainAll(collection); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public Object[] toArray() { + return delegate().toArray(); + } + + @Override + public T[] toArray(T[] array) { + return delegate().toArray(array); + } + + /** + * A sensible definition of {@link #contains} in terms of {@link #iterator}. + * If you override {@link #iterator}, you may wish to override {@link + * #contains} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardContains(@Nullable Object object) { + return Iterators.contains(iterator(), object); + } + + /** + * A sensible definition of {@link #containsAll} in terms of {@link #contains} + * . If you override {@link #contains}, you may wish to override {@link + * #containsAll} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardContainsAll(Collection collection) { + for (Object o : collection) { + if (!contains(o)) { + return false; + } + } + return true; + } + + /** + * A sensible definition of {@link #addAll} in terms of {@link #add}. If you + * override {@link #add}, you may wish to override {@link #addAll} to forward + * to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardAddAll(Collection collection) { + return Iterators.addAll(this, collection.iterator()); + } + + /** + * A sensible definition of {@link #remove} in terms of {@link #iterator}, + * using the iterator's {@code remove} method. If you override {@link + * #iterator}, you may wish to override {@link #remove} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardRemove(@Nullable Object object) { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + if (Objects.equal(iterator.next(), object)) { + iterator.remove(); + return true; + } + } + return false; + } + + /** + * A sensible definition of {@link #removeAll} in terms of {@link #iterator}, + * using the iterator's {@code remove} method. If you override {@link + * #iterator}, you may wish to override {@link #removeAll} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardRemoveAll(Collection collection) { + return Iterators.removeAll(iterator(), collection); + } + + /** + * A sensible definition of {@link #retainAll} in terms of {@link #iterator}, + * using the iterator's {@code remove} method. If you override {@link + * #iterator}, you may wish to override {@link #retainAll} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardRetainAll(Collection collection) { + return Iterators.retainAll(iterator(), collection); + } + + /** + * A sensible definition of {@link #clear} in terms of {@link #iterator}, + * using the iterator's {@code remove} method. If you override {@link + * #iterator}, you may wish to override {@link #clear} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected void standardClear() { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } + + /** + * A sensible definition of {@link #isEmpty} as {@code !iterator().hasNext}. + * If you override {@link #isEmpty}, you may wish to override {@link #isEmpty} + * to forward to this implementation. Alternately, it may be more efficient to + * implement {@code isEmpty} as {@code size() == 0}. + * + * @since 7.0 + */ + @Beta protected boolean standardIsEmpty() { + return !iterator().hasNext(); + } + + /** + * A sensible definition of {@link #toString} in terms of {@link #iterator}. + * If you override {@link #iterator}, you may wish to override {@link + * #toString} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected String standardToString() { + return Collections2.toStringImpl(this); + } + + /** + * A sensible definition of {@link #toArray()} in terms of {@link + * #toArray(Object[])}. If you override {@link #toArray(Object[])}, you may + * wish to override {@link #toArray} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected Object[] standardToArray() { + Object[] newArray = new Object[size()]; + return toArray(newArray); + } + + /** + * A sensible definition of {@link #toArray(Object[])} in terms of {@link + * #size} and {@link #iterator}. If you override either of these methods, you + * may wish to override {@link #toArray} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected T[] standardToArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingConcurrentMap.java b/guava/src/com/google/common/collect/ForwardingConcurrentMap.java new file mode 100644 index 0000000..0336dd1 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingConcurrentMap.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.concurrent.ConcurrentMap; + +/** + * A concurrent map which forwards all its method calls to another concurrent + * map. Subclasses should override one or more methods to modify the behavior of + * the backing map as desired per the decorator pattern. + * + * @author Charles Fry + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingConcurrentMap extends ForwardingMap + implements ConcurrentMap { + + /** Constructor for use by subclasses. */ + protected ForwardingConcurrentMap() {} + + @Override protected abstract ConcurrentMap delegate(); + + @Override + public V putIfAbsent(K key, V value) { + return delegate().putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return delegate().remove(key, value); + } + + @Override + public V replace(K key, V value) { + return delegate().replace(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return delegate().replace(key, oldValue, newValue); + } + +} diff --git a/guava/src/com/google/common/collect/ForwardingDeque.java b/guava/src/com/google/common/collect/ForwardingDeque.java new file mode 100644 index 0000000..27d63f3 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingDeque.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; + +import java.util.Deque; +import java.util.Iterator; + +/** + * A deque which forwards all its method calls to another deque. Subclasses + * should override one or more methods to modify the behavior of the backing + * deque as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingDeque} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #offer} which can lead to unexpected behavior. In this case, you should + * override {@code offer} as well, either providing your own implementation, or + * delegating to the provided {@code standardOffer} method. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even + * when all of the methods that they depend on are thread-safe. + * + * @author Kurt Alfred Kluever + * @since 12.0 + */ +@Beta +public abstract class ForwardingDeque extends ForwardingQueue + implements Deque { + + /** Constructor for use by subclasses. */ + protected ForwardingDeque() {} + + @Override protected abstract Deque delegate(); + + @Override + public void addFirst(E e) { + delegate().addFirst(e); + } + + @Override + public void addLast(E e) { + delegate().addLast(e); + } + + @Override + public Iterator descendingIterator() { + return delegate().descendingIterator(); + } + + @Override + public E getFirst() { + return delegate().getFirst(); + } + + @Override + public E getLast() { + return delegate().getLast(); + } + + @Override + public boolean offerFirst(E e) { + return delegate().offerFirst(e); + } + + @Override + public boolean offerLast(E e) { + return delegate().offerLast(e); + } + + @Override + public E peekFirst() { + return delegate().peekFirst(); + } + + @Override + public E peekLast() { + return delegate().peekLast(); + } + + @Override + public E pollFirst() { + return delegate().pollFirst(); + } + + @Override + public E pollLast() { + return delegate().pollLast(); + } + + @Override + public E pop() { + return delegate().pop(); + } + + @Override + public void push(E e) { + delegate().push(e); + } + + @Override + public E removeFirst() { + return delegate().removeFirst(); + } + + @Override + public E removeLast() { + return delegate().removeLast(); + } + + @Override + public boolean removeFirstOccurrence(Object o) { + return delegate().removeFirstOccurrence(o); + } + + @Override + public boolean removeLastOccurrence(Object o) { + return delegate().removeLastOccurrence(o); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingImmutableList.java b/guava/src/com/google/common/collect/ForwardingImmutableList.java new file mode 100644 index 0000000..2b9092e --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingImmutableList.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableList { + private ForwardingImmutableList() {} +} diff --git a/guava/src/com/google/common/collect/ForwardingImmutableMap.java b/guava/src/com/google/common/collect/ForwardingImmutableMap.java new file mode 100644 index 0000000..a367157 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingImmutableMap.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableMap { + private ForwardingImmutableMap() {} +} diff --git a/guava/src/com/google/common/collect/ForwardingImmutableSet.java b/guava/src/com/google/common/collect/ForwardingImmutableSet.java new file mode 100644 index 0000000..c7d7bf6 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingImmutableSet.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableSet { + private ForwardingImmutableSet() {} +} diff --git a/guava/src/com/google/common/collect/ForwardingIterator.java b/guava/src/com/google/common/collect/ForwardingIterator.java new file mode 100644 index 0000000..b8927d4 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingIterator.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator which forwards all its method calls to another iterator. + * Subclasses should override one or more methods to modify the behavior of the + * backing iterator as desired per the decorator pattern. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingIterator + extends ForwardingObject implements Iterator { + + /** Constructor for use by subclasses. */ + protected ForwardingIterator() {} + + @Override protected abstract Iterator delegate(); + + @Override + public boolean hasNext() { + return delegate().hasNext(); + } + + @Override + public T next() { + return delegate().next(); + } + + @Override + public void remove() { + delegate().remove(); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingList.java b/guava/src/com/google/common/collect/ForwardingList.java new file mode 100644 index 0000000..49d5c5a --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingList.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import javax.annotation.Nullable; + +/** + * A list which forwards all its method calls to another list. Subclasses should + * override one or more methods to modify the behavior of the backing list as + * desired per the decorator pattern. + * + *

This class does not implement {@link java.util.RandomAccess}. If the + * delegate supports random access, the {@code ForwardingList} subclass should + * implement the {@code RandomAccess} interface. + * + *

Warning: The methods of {@code ForwardingList} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardAddAll} method. + * + *

The {@code standard} methods and any collection views they return are not + * guaranteed to be thread-safe, even when all of the methods that they depend + * on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingList extends ForwardingCollection + implements List { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingList() {} + + @Override protected abstract List delegate(); + + @Override + public void add(int index, E element) { + delegate().add(index, element); + } + + @Override + public boolean addAll(int index, Collection elements) { + return delegate().addAll(index, elements); + } + + @Override + public E get(int index) { + return delegate().get(index); + } + + @Override + public int indexOf(Object element) { + return delegate().indexOf(element); + } + + @Override + public int lastIndexOf(Object element) { + return delegate().lastIndexOf(element); + } + + @Override + public ListIterator listIterator() { + return delegate().listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return delegate().listIterator(index); + } + + @Override + public E remove(int index) { + return delegate().remove(index); + } + + @Override + public E set(int index, E element) { + return delegate().set(index, element); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return delegate().subList(fromIndex, toIndex); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible default implementation of {@link #add(Object)}, in terms of + * {@link #add(int, Object)}. If you override {@link #add(int, Object)}, you + * may wish to override {@link #add(Object)} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardAdd(E element){ + add(size(), element); + return true; + } + + /** + * A sensible default implementation of {@link #addAll(int, Collection)}, in + * terms of the {@code add} method of {@link #listIterator(int)}. If you + * override {@link #listIterator(int)}, you may wish to override {@link + * #addAll(int, Collection)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardAddAll( + int index, Iterable elements) { + return Lists.addAllImpl(this, index, elements); + } + + /** + * A sensible default implementation of {@link #indexOf}, in terms of {@link + * #listIterator()}. If you override {@link #listIterator()}, you may wish to + * override {@link #indexOf} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardIndexOf(@Nullable Object element) { + return Lists.indexOfImpl(this, element); + } + + /** + * A sensible default implementation of {@link #lastIndexOf}, in terms of + * {@link #listIterator(int)}. If you override {@link #listIterator(int)}, you + * may wish to override {@link #lastIndexOf} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected int standardLastIndexOf(@Nullable Object element) { + return Lists.lastIndexOfImpl(this, element); + } + + /** + * A sensible default implementation of {@link #iterator}, in terms of + * {@link #listIterator()}. If you override {@link #listIterator()}, you may + * wish to override {@link #iterator} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected Iterator standardIterator() { + return listIterator(); + } + + /** + * A sensible default implementation of {@link #listIterator()}, in terms of + * {@link #listIterator(int)}. If you override {@link #listIterator(int)}, you + * may wish to override {@link #listIterator()} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected ListIterator standardListIterator() { + return listIterator(0); + } + + /** + * A sensible default implementation of {@link #listIterator(int)}, in terms + * of {@link #size}, {@link #get(int)}, {@link #set(int, Object)}, {@link + * #add(int, Object)}, and {@link #remove(int)}. If you override any of these + * methods, you may wish to override {@link #listIterator(int)} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta protected ListIterator standardListIterator(int start) { + return Lists.listIteratorImpl(this, start); + } + + /** + * A sensible default implementation of {@link #subList(int, int)}. If you + * override any other methods, you may wish to override {@link #subList(int, + * int)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected List standardSubList(int fromIndex, int toIndex) { + return Lists.subListImpl(this, fromIndex, toIndex); + } + + /** + * A sensible definition of {@link #equals(Object)} in terms of {@link #size} + * and {@link #iterator}. If you override either of those methods, you may + * wish to override {@link #equals(Object)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardEquals(@Nullable Object object) { + return Lists.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of {@link #iterator}. + * If you override {@link #iterator}, you may wish to override {@link + * #hashCode} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardHashCode() { + return Lists.hashCodeImpl(this); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingListIterator.java b/guava/src/com/google/common/collect/ForwardingListIterator.java new file mode 100644 index 0000000..e2ebe55 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingListIterator.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.ListIterator; + +/** + * A list iterator which forwards all its method calls to another list + * iterator. Subclasses should override one or more methods to modify the + * behavior of the backing iterator as desired per the decorator pattern. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingListIterator extends ForwardingIterator + implements ListIterator { + + /** Constructor for use by subclasses. */ + protected ForwardingListIterator() {} + + @Override protected abstract ListIterator delegate(); + + @Override + public void add(E element) { + delegate().add(element); + } + + @Override + public boolean hasPrevious() { + return delegate().hasPrevious(); + } + + @Override + public int nextIndex() { + return delegate().nextIndex(); + } + + @Override + public E previous() { + return delegate().previous(); + } + + @Override + public int previousIndex() { + return delegate().previousIndex(); + } + + @Override + public void set(E element) { + delegate().set(element); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingListMultimap.java b/guava/src/com/google/common/collect/ForwardingListMultimap.java new file mode 100644 index 0000000..3b1d55e --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingListMultimap.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * A list multimap which forwards all its method calls to another list multimap. + * Subclasses should override one or more methods to modify the behavior of + * the backing multimap as desired per the decorator pattern. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingListMultimap + extends ForwardingMultimap implements ListMultimap { + + /** Constructor for use by subclasses. */ + protected ForwardingListMultimap() {} + + @Override protected abstract ListMultimap delegate(); + + @Override public List get(@Nullable K key) { + return delegate().get(key); + } + + @Override public List removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + @Override public List replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingMap.java b/guava/src/com/google/common/collect/ForwardingMap.java new file mode 100644 index 0000000..a332b22 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingMap.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A map which forwards all its method calls to another map. Subclasses should + * override one or more methods to modify the behavior of the backing map as + * desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMap} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #put} alone will not change the behavior of {@link + * #putAll}, which can lead to unexpected behavior. In this case, you should + * override {@code putAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardPutAll} method. + * + *

Each of the {@code standard} methods, where appropriate, use {@link + * Objects#equal} to test equality for both keys and values. This may not be + * the desired behavior for map implementations that use non-standard notions of + * key equality, such as a {@code SortedMap} whose comparator is not consistent + * with {@code equals}. + * + *

The {@code standard} methods and the collection views they return are not + * guaranteed to be thread-safe, even when all of the methods that they depend + * on are thread-safe. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingMap extends ForwardingObject + implements Map { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingMap() {} + + @Override protected abstract Map delegate(); + + @Override + public int size() { + return delegate().size(); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public V remove(Object object) { + return delegate().remove(object); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public boolean containsKey(Object key) { + return delegate().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + @Override + public V get(Object key) { + return delegate().get(key); + } + + @Override + public V put(K key, V value) { + return delegate().put(key, value); + } + + @Override + public void putAll(Map map) { + delegate().putAll(map); + } + + @Override + public Set keySet() { + return delegate().keySet(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #putAll(Map)} in terms of {@link + * #put(Object, Object)}. If you override {@link #put(Object, Object)}, you + * may wish to override {@link #putAll(Map)} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected void standardPutAll(Map map) { + Maps.putAllImpl(this, map); + } + + /** + * A sensible, albeit inefficient, definition of {@link #remove} in terms of + * the {@code iterator} method of {@link #entrySet}. If you override {@link + * #entrySet}, you may wish to override {@link #remove} to forward to this + * implementation. + * + *

Alternately, you may wish to override {@link #remove} with {@code + * keySet().remove}, assuming that approach would not lead to an infinite + * loop. + * + * @since 7.0 + */ + @Beta protected V standardRemove(@Nullable Object key) { + Iterator> entryIterator = entrySet().iterator(); + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + if (Objects.equal(entry.getKey(), key)) { + V value = entry.getValue(); + entryIterator.remove(); + return value; + } + } + return null; + } + + /** + * A sensible definition of {@link #clear} in terms of the {@code iterator} + * method of {@link #entrySet}. In many cases, you may wish to override + * {@link #clear} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected void standardClear() { + Iterator> entryIterator = entrySet().iterator(); + while (entryIterator.hasNext()) { + entryIterator.next(); + entryIterator.remove(); + } + } + + /** + * A sensible implementation of {@link Map#keySet} in terms of the following + * methods: {@link ForwardingMap#clear}, {@link ForwardingMap#containsKey}, + * {@link ForwardingMap#isEmpty}, {@link ForwardingMap#remove}, {@link + * ForwardingMap#size}, and the {@link Set#iterator} method of {@link + * ForwardingMap#entrySet}. In many cases, you may wish to override {@link + * ForwardingMap#keySet} to forward to this implementation or a subclass + * thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardKeySet extends Maps.KeySet { + /** Constructor for use by subclasses. */ + public StandardKeySet() {} + + @Override + Map map() { + return ForwardingMap.this; + } + } + + /** + * A sensible, albeit inefficient, definition of {@link #containsKey} in terms + * of the {@code iterator} method of {@link #entrySet}. If you override {@link + * #entrySet}, you may wish to override {@link #containsKey} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardContainsKey(@Nullable Object key) { + return Maps.containsKeyImpl(this, key); + } + + /** + * A sensible implementation of {@link Map#values} in terms of the following + * methods: {@link ForwardingMap#clear}, {@link ForwardingMap#containsValue}, + * {@link ForwardingMap#isEmpty}, {@link ForwardingMap#size}, and the {@link + * Set#iterator} method of {@link ForwardingMap#entrySet}. In many cases, you + * may wish to override {@link ForwardingMap#values} to forward to this + * implementation or a subclass thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardValues extends Maps.Values { + /** Constructor for use by subclasses. */ + public StandardValues() {} + + @Override + Map map() { + return ForwardingMap.this; + } + } + + /** + * A sensible definition of {@link #containsValue} in terms of the {@code + * iterator} method of {@link #entrySet}. If you override {@link #entrySet}, + * you may wish to override {@link #containsValue} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardContainsValue(@Nullable Object value) { + return Maps.containsValueImpl(this, value); + } + + /** + * A sensible implementation of {@link Map#entrySet} in terms of the following + * methods: {@link ForwardingMap#clear}, {@link ForwardingMap#containsKey}, + * {@link ForwardingMap#get}, {@link ForwardingMap#isEmpty}, {@link + * ForwardingMap#remove}, and {@link ForwardingMap#size}. In many cases, you + * may wish to override {@link #entrySet} to forward to this implementation + * or a subclass thereof. + * + * @since 10.0 + */ + @Beta + protected abstract class StandardEntrySet extends Maps.EntrySet { + /** Constructor for use by subclasses. */ + public StandardEntrySet() {} + + @Override + Map map() { + return ForwardingMap.this; + } + } + + /** + * A sensible definition of {@link #isEmpty} in terms of the {@code iterator} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may + * wish to override {@link #isEmpty} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardIsEmpty() { + return !entrySet().iterator().hasNext(); + } + + /** + * A sensible definition of {@link #equals} in terms of the {@code equals} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may + * wish to override {@link #equals} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardEquals(@Nullable Object object) { + return Maps.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of the {@code iterator} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may + * wish to override {@link #hashCode} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardHashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + /** + * A sensible definition of {@link #toString} in terms of the {@code iterator} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may + * wish to override {@link #toString} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected String standardToString() { + return Maps.toStringImpl(this); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingMapEntry.java b/guava/src/com/google/common/collect/ForwardingMapEntry.java new file mode 100644 index 0000000..ff201a5 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingMapEntry.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * A map entry which forwards all its method calls to another map entry. + * Subclasses should override one or more methods to modify the behavior of the + * backing map entry as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMapEntry} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #getValue} alone will not change the behavior of + * {@link #equals}, which can lead to unexpected behavior. In this case, you + * should override {@code equals} as well, either providing your own + * implementation, or delegating to the provided {@code standardEquals} method. + * + *

Each of the {@code standard} methods, where appropriate, use {@link + * Objects#equal} to test equality for both keys and values. This may not be + * the desired behavior for map implementations that use non-standard notions of + * key equality, such as the entry of a {@code SortedMap} whose comparator is + * not consistent with {@code equals}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even + * when all of the methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingMapEntry + extends ForwardingObject implements Map.Entry { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingMapEntry() {} + + @Override protected abstract Map.Entry delegate(); + + @Override + public K getKey() { + return delegate().getKey(); + } + + @Override + public V getValue() { + return delegate().getValue(); + } + + @Override + public V setValue(V value) { + return delegate().setValue(value); + } + + @Override public boolean equals(@Nullable Object object) { + return delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #equals(Object)} in terms of {@link + * #getKey()} and {@link #getValue()}. If you override either of these + * methods, you may wish to override {@link #equals(Object)} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardEquals(@Nullable Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + /** + * A sensible definition of {@link #hashCode()} in terms of {@link #getKey()} + * and {@link #getValue()}. If you override either of these methods, you may + * wish to override {@link #hashCode()} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardHashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * A sensible definition of {@link #toString} in terms of {@link + * #getKey} and {@link #getValue}. If you override either of these + * methods, you may wish to override {@link #equals} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected String standardToString() { + return getKey() + "=" + getValue(); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingMultimap.java b/guava/src/com/google/common/collect/ForwardingMultimap.java new file mode 100644 index 0000000..c860820 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingMultimap.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A multimap which forwards all its method calls to another multimap. + * Subclasses should override one or more methods to modify the behavior of + * the backing multimap as desired per the decorator pattern. + * + * @author Robert Konigsberg + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingMultimap extends ForwardingObject + implements Multimap { + + /** Constructor for use by subclasses. */ + protected ForwardingMultimap() {} + + @Override protected abstract Multimap delegate(); + + @Override + public Map> asMap() { + return delegate().asMap(); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + return delegate().containsEntry(key, value); + } + + @Override + public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + @Override + public boolean containsValue(@Nullable Object value) { + return delegate().containsValue(value); + } + + @Override + public Collection> entries() { + return delegate().entries(); + } + + @Override + public Collection get(@Nullable K key) { + return delegate().get(key); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public Multiset keys() { + return delegate().keys(); + } + + @Override + public Set keySet() { + return delegate().keySet(); + } + + @Override + public boolean put(K key, V value) { + return delegate().put(key, value); + } + + @Override + public boolean putAll(K key, Iterable values) { + return delegate().putAll(key, values); + } + + @Override + public boolean putAll(Multimap multimap) { + return delegate().putAll(multimap); + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + return delegate().remove(key, value); + } + + @Override + public Collection removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + @Override + public Collection replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingMultiset.java b/guava/src/com/google/common/collect/ForwardingMultiset.java new file mode 100644 index 0000000..a1aa9c0 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingMultiset.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A multiset which forwards all its method calls to another multiset. + * Subclasses should override one or more methods to modify the behavior of the + * backing multiset as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMultiset} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add(Object, int)} alone will not change the + * behavior of {@link #add(Object)}, which can lead to unexpected behavior. In + * this case, you should override {@code add(Object)} as well, either providing + * your own implementation, or delegating to the provided {@code standardAdd} + * method. + * + *

The {@code standard} methods and any collection views they return are not + * guaranteed to be thread-safe, even when all of the methods that they depend + * on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingMultiset extends ForwardingCollection + implements Multiset { + + /** Constructor for use by subclasses. */ + protected ForwardingMultiset() {} + + @Override protected abstract Multiset delegate(); + + @Override + public int count(Object element) { + return delegate().count(element); + } + + @Override + public int add(E element, int occurrences) { + return delegate().add(element, occurrences); + } + + @Override + public int remove(Object element, int occurrences) { + return delegate().remove(element, occurrences); + } + + @Override + public Set elementSet() { + return delegate().elementSet(); + } + + @Override + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + @Override + public int setCount(E element, int count) { + return delegate().setCount(element, count); + } + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + return delegate().setCount(element, oldCount, newCount); + } + + /** + * A sensible definition of {@link #contains} in terms of {@link #count}. If + * you override {@link #count}, you may wish to override {@link #contains} to + * forward to this implementation. + * + * @since 7.0 + */ + @Override @Beta protected boolean standardContains(@Nullable Object object) { + return count(object) > 0; + } + + /** + * A sensible definition of {@link #clear} in terms of the {@code iterator} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may + * wish to override {@link #clear} to forward to this implementation. + * + * @since 7.0 + */ + @Override @Beta protected void standardClear() { + Iterator> entryIterator = entrySet().iterator(); + while (entryIterator.hasNext()) { + entryIterator.next(); + entryIterator.remove(); + } + } + + /** + * A sensible, albeit inefficient, definition of {@link #count} in terms of + * {@link #entrySet}. If you override {@link #entrySet}, you may wish to + * override {@link #count} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardCount(@Nullable Object object) { + for (Entry entry : this.entrySet()) { + if (Objects.equal(entry.getElement(), object)) { + return entry.getCount(); + } + } + return 0; + } + + /** + * A sensible definition of {@link #add(Object)} in terms of {@link + * #add(Object, int)}. If you override {@link #add(Object, int)}, you may + * wish to override {@link #add(Object)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardAdd(E element) { + add(element, 1); + return true; + } + + /** + * A sensible definition of {@link #addAll(Collection)} in terms of {@link + * #add(Object)} and {@link #add(Object, int)}. If you override either of + * these methods, you may wish to override {@link #addAll(Collection)} to + * forward to this implementation. + * + * @since 7.0 + */ + @Beta @Override protected boolean standardAddAll( + Collection elementsToAdd) { + return Multisets.addAllImpl(this, elementsToAdd); + } + + /** + * A sensible definition of {@link #remove(Object)} in terms of {@link + * #remove(Object, int)}. If you override {@link #remove(Object, int)}, you + * may wish to override {@link #remove(Object)} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta @Override protected boolean standardRemove(Object element) { + return remove(element, 1) > 0; + } + + /** + * A sensible definition of {@link #removeAll} in terms of the {@code + * removeAll} method of {@link #elementSet}. If you override {@link + * #elementSet}, you may wish to override {@link #removeAll} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta @Override protected boolean standardRemoveAll( + Collection elementsToRemove) { + return Multisets.removeAllImpl(this, elementsToRemove); + } + + /** + * A sensible definition of {@link #retainAll} in terms of the {@code + * retainAll} method of {@link #elementSet}. If you override {@link + * #elementSet}, you may wish to override {@link #retainAll} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta @Override protected boolean standardRetainAll( + Collection elementsToRetain) { + return Multisets.retainAllImpl(this, elementsToRetain); + } + + /** + * A sensible definition of {@link #setCount(Object, int)} in terms of {@link + * #count(Object)}, {@link #add(Object, int)}, and {@link #remove(Object, + * int)}. {@link #entrySet()}. If you override any of these methods, you may + * wish to override {@link #setCount(Object, int)} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected int standardSetCount(E element, int count) { + return Multisets.setCountImpl(this, element, count); + } + + /** + * A sensible definition of {@link #setCount(Object, int, int)} in terms of + * {@link #count(Object)} and {@link #setCount(Object, int)}. If you override + * either of these methods, you may wish to override {@link #setCount(Object, + * int, int)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardSetCount( + E element, int oldCount, int newCount) { + return Multisets.setCountImpl(this, element, oldCount, newCount); + } + + /** + * A sensible implementation of {@link Multiset#elementSet} in terms of the + * following methods: {@link ForwardingMultiset#clear}, {@link + * ForwardingMultiset#contains}, {@link ForwardingMultiset#containsAll}, + * {@link ForwardingMultiset#count}, {@link ForwardingMultiset#isEmpty}, the + * {@link Set#size} and {@link Set#iterator} methods of {@link + * ForwardingMultiset#entrySet}, and {@link ForwardingMultiset#remove(Object, + * int)}. In many situations, you may wish to override {@link + * ForwardingMultiset#elementSet} to forward to this implementation or a + * subclass thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardElementSet extends Multisets.ElementSet { + /** Constructor for use by subclasses. */ + public StandardElementSet() {} + + @Override + Multiset multiset() { + return ForwardingMultiset.this; + } + } + + /** + * A sensible definition of {@link #iterator} in terms of {@link #entrySet} + * and {@link #remove(Object)}. If you override either of these methods, you + * may wish to override {@link #iterator} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected Iterator standardIterator() { + return Multisets.iteratorImpl(this); + } + + /** + * A sensible, albeit inefficient, definition of {@link #size} in terms of + * {@link #entrySet}. If you override {@link #entrySet}, you may wish to + * override {@link #size} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardSize() { + return Multisets.sizeImpl(this); + } + + /** + * A sensible, albeit inefficient, definition of {@link #size} in terms of + * {@code entrySet().size()} and {@link #count}. If you override either of + * these methods, you may wish to override {@link #size} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardEquals(@Nullable Object object) { + return Multisets.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} as {@code entrySet().hashCode()} + * . If you override {@link #entrySet}, you may wish to override {@link + * #hashCode} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardHashCode() { + return entrySet().hashCode(); + } + + /** + * A sensible definition of {@link #toString} as {@code entrySet().toString()} + * . If you override {@link #entrySet}, you may wish to override {@link + * #toString} to forward to this implementation. + * + * @since 7.0 + */ + @Beta @Override protected String standardToString() { + return entrySet().toString(); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingNavigableMap.java b/guava/src/com/google/common/collect/ForwardingNavigableMap.java new file mode 100644 index 0000000..bf9388b --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingNavigableMap.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Maps.keyOrNull; + +import com.google.common.annotations.Beta; + +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.SortedMap; + +import javax.annotation.Nullable; + +/** + * A navigable map which forwards all its method calls to another navigable map. Subclasses should + * override one or more methods to modify the behavior of the backing map as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingNavigableMap} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #put} alone will not + * change the behavior of {@link #putAll}, which can lead to unexpected behavior. In this case, you + * should override {@code putAll} as well, either providing your own implementation, or delegating + * to the provided {@code standardPutAll} method. + * + *

Each of the {@code standard} methods uses the map's comparator (or the natural ordering of + * the elements, if there is no comparator) to test element equality. As a result, if the comparator + * is not consistent with equals, some of the standard implementations may violate the {@code Map} + * contract. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Louis Wasserman + * @since 12.0 + */ +@Beta +public abstract class ForwardingNavigableMap + extends ForwardingSortedMap implements NavigableMap { + + /** Constructor for use by subclasses. */ + protected ForwardingNavigableMap() {} + + @Override + protected abstract NavigableMap delegate(); + + @Override + public Entry lowerEntry(K key) { + return delegate().lowerEntry(key); + } + + /** + * A sensible definition of {@link #lowerEntry} in terms of the {@code lastEntry()} of + * {@link #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override + * {@code lowerEntry} to forward to this implementation. + */ + protected Entry standardLowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return delegate().lowerKey(key); + } + + /** + * A sensible definition of {@link #lowerKey} in terms of {@code lowerEntry}. If you override + * {@link #lowerEntry}, you may wish to override {@code lowerKey} to forward to this + * implementation. + */ + protected K standardLowerKey(K key) { + return keyOrNull(lowerEntry(key)); + } + + @Override + public Entry floorEntry(K key) { + return delegate().floorEntry(key); + } + + /** + * A sensible definition of {@link #floorEntry} in terms of the {@code lastEntry()} of + * {@link #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override + * {@code floorEntry} to forward to this implementation. + */ + protected Entry standardFloorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public K floorKey(K key) { + return delegate().floorKey(key); + } + + /** + * A sensible definition of {@link #floorKey} in terms of {@code floorEntry}. If you override + * {@code floorEntry}, you may wish to override {@code floorKey} to forward to this + * implementation. + */ + protected K standardFloorKey(K key) { + return keyOrNull(floorEntry(key)); + } + + @Override + public Entry ceilingEntry(K key) { + return delegate().ceilingEntry(key); + } + + /** + * A sensible definition of {@link #ceilingEntry} in terms of the {@code firstEntry()} of + * {@link #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override + * {@code ceilingEntry} to forward to this implementation. + */ + protected Entry standardCeilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return delegate().ceilingKey(key); + } + + /** + * A sensible definition of {@link #ceilingKey} in terms of {@code ceilingEntry}. If you override + * {@code ceilingEntry}, you may wish to override {@code ceilingKey} to forward to this + * implementation. + */ + protected K standardCeilingKey(K key) { + return keyOrNull(ceilingEntry(key)); + } + + @Override + public Entry higherEntry(K key) { + return delegate().higherEntry(key); + } + + /** + * A sensible definition of {@link #higherEntry} in terms of the {@code firstEntry()} of + * {@link #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override + * {@code higherEntry} to forward to this implementation. + */ + protected Entry standardHigherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return delegate().higherKey(key); + } + + /** + * A sensible definition of {@link #higherKey} in terms of {@code higherEntry}. If you override + * {@code higherEntry}, you may wish to override {@code higherKey} to forward to this + * implementation. + */ + protected K standardHigherKey(K key) { + return keyOrNull(higherEntry(key)); + } + + @Override + public Entry firstEntry() { + return delegate().firstEntry(); + } + + /** + * A sensible definition of {@link #firstEntry} in terms of the {@code iterator()} of + * {@link #entrySet}. If you override {@code entrySet}, you may wish to override + * {@code firstEntry} to forward to this implementation. + */ + protected Entry standardFirstEntry() { + return Iterables.getFirst(entrySet(), null); + } + + /** + * A sensible definition of {@link #firstKey} in terms of {@code firstEntry}. If you override + * {@code firstEntry}, you may wish to override {@code firstKey} to forward to this + * implementation. + */ + protected K standardFirstKey() { + Entry entry = firstEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public Entry lastEntry() { + return delegate().lastEntry(); + } + + /** + * A sensible definition of {@link #lastEntry} in terms of the {@code iterator()} of the + * {@link #entrySet} of {@link #descendingMap}. If you override {@code descendingMap}, you may + * wish to override {@code lastEntry} to forward to this implementation. + */ + protected Entry standardLastEntry() { + return Iterables.getFirst(descendingMap().entrySet(), null); + } + + /** + * A sensible definition of {@link #lastKey} in terms of {@code lastEntry}. If you override + * {@code lastEntry}, you may wish to override {@code lastKey} to forward to this implementation. + */ + protected K standardLastKey() { + Entry entry = lastEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public Entry pollFirstEntry() { + return delegate().pollFirstEntry(); + } + + /** + * A sensible definition of {@link #pollFirstEntry} in terms of the {@code iterator} of + * {@code entrySet}. If you override {@code entrySet}, you may wish to override + * {@code pollFirstEntry} to forward to this implementation. + */ + protected Entry standardPollFirstEntry() { + return poll(entrySet().iterator()); + } + + @Override + public Entry pollLastEntry() { + return delegate().pollLastEntry(); + } + + /** + * A sensible definition of {@link #pollFirstEntry} in terms of the {@code iterator} of the + * {@code entrySet} of {@code descendingMap}. If you override {@code descendingMap}, you may wish + * to override {@code pollFirstEntry} to forward to this implementation. + */ + protected Entry standardPollLastEntry() { + return poll(descendingMap().entrySet().iterator()); + } + + @Override + public NavigableMap descendingMap() { + return delegate().descendingMap(); + } + + /** + * A sensible implementation of {@link NavigableMap#descendingMap} in terms of the methods of + * this {@code NavigableMap}. In many cases, you may wish to override + * {@link ForwardingNavigableMap#descendingMap} to forward to this implementation or a subclass + * thereof. + * + *

In particular, this map iterates over entries with repeated calls to + * {@link NavigableMap#lowerEntry}. If a more efficient means of iteration is available, you may + * wish to override the {@code entryIterator()} method of this class. + * + * @since 12.0 + */ + @Beta + protected class StandardDescendingMap extends Maps.DescendingMap { + /** Constructor for use by subclasses. */ + public StandardDescendingMap() {} + + @Override + NavigableMap forward() { + return ForwardingNavigableMap.this; + } + + @Override + protected Iterator> entryIterator() { + return new Iterator>() { + private Entry toRemove = null; + private Entry nextOrNull = forward().lastEntry(); + + @Override + public boolean hasNext() { + return nextOrNull != null; + } + + @Override + public java.util.Map.Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return nextOrNull; + } finally { + toRemove = nextOrNull; + nextOrNull = forward().lowerEntry(nextOrNull.getKey()); + } + } + + @Override + public void remove() { + Iterators.checkRemove(toRemove != null); + forward().remove(toRemove.getKey()); + toRemove = null; + } + }; + } + } + + @Override + public NavigableSet navigableKeySet() { + return delegate().navigableKeySet(); + } + + /** + * A sensible implementation of {@link NavigableMap#navigableKeySet} in terms of the methods of + * this {@code NavigableMap}. In many cases, you may wish to override + * {@link ForwardingNavigableMap#navigableKeySet} to forward to this implementation or a subclass + * thereof. + * + * @since 12.0 + */ + @Beta + protected class StandardNavigableKeySet extends Maps.NavigableKeySet { + /** Constructor for use by subclasses. */ + public StandardNavigableKeySet() {} + + @Override + NavigableMap map() { + return ForwardingNavigableMap.this; + } + } + + @Override + public NavigableSet descendingKeySet() { + return delegate().descendingKeySet(); + } + + /** + * A sensible definition of {@link #descendingKeySet} as the {@code navigableKeySet} of + * {@link #descendingMap}. (The {@link StandardDescendingMap} implementation implements + * {@code navigableKeySet} on its own, so as not to cause an infinite loop.) If you override + * {@code descendingMap}, you may wish to override {@code descendingKeySet} to forward to this + * implementation. + */ + protected NavigableSet standardDescendingKeySet() { + return descendingMap().navigableKeySet(); + } + + /** + * A sensible definition of {@link #subMap(Object, Object)} in terms of + * {@link #subMap(Object, boolean, Object, boolean)}. If you override + * {@code subMap(K, boolean, K, boolean)}, you may wish to override {@code subMap} to forward to + * this implementation. + */ + @Override + protected SortedMap standardSubMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return delegate().subMap(fromKey, fromInclusive, toKey, toInclusive); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return delegate().headMap(toKey, inclusive); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return delegate().tailMap(fromKey, inclusive); + } + + /** + * A sensible definition of {@link #headMap(Object)} in terms of + * {@link #headMap(Object, boolean)}. If you override {@code headMap(K, boolean)}, you may wish + * to override {@code headMap} to forward to this implementation. + */ + protected SortedMap standardHeadMap(K toKey) { + return headMap(toKey, false); + } + + /** + * A sensible definition of {@link #tailMap(Object)} in terms of + * {@link #tailMap(Object, boolean)}. If you override {@code tailMap(K, boolean)}, you may wish + * to override {@code tailMap} to forward to this implementation. + */ + protected SortedMap standardTailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Nullable + private static T poll(Iterator iterator) { + if (iterator.hasNext()) { + T result = iterator.next(); + iterator.remove(); + return result; + } + return null; + } +} diff --git a/guava/src/com/google/common/collect/ForwardingNavigableSet.java b/guava/src/com/google/common/collect/ForwardingNavigableSet.java new file mode 100644 index 0000000..f89b1fb --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingNavigableSet.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; + +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A navigable set which forwards all its method calls to another navigable set. Subclasses should + * override one or more methods to modify the behavior of the backing set as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingNavigableSet} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #add} alone will not + * change the behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you + * should override {@code addAll} as well, either providing your own implementation, or delegating + * to the provided {@code standardAddAll} method. + * + *

Each of the {@code standard} methods uses the set's comparator (or the natural ordering of + * the elements, if there is no comparator) to test element equality. As a result, if the + * comparator is not consistent with equals, some of the standard implementations may violate the + * {@code Set} contract. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Louis Wasserman + * @since 12.0 + */ +@Beta +public abstract class ForwardingNavigableSet + extends ForwardingSortedSet implements NavigableSet { + + /** Constructor for use by subclasses. */ + protected ForwardingNavigableSet() {} + + @Override + protected abstract NavigableSet delegate(); + + @Override + public E lower(E e) { + return delegate().lower(e); + } + + /** + * A sensible definition of {@link #lower} in terms of the {@code descendingIterator} method of + * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may + * wish to override {@link #lower} to forward to this implementation. + */ + protected E standardLower(E e) { + return Iterators.getNext(headSet(e, false).descendingIterator(), null); + } + + @Override + public E floor(E e) { + return delegate().floor(e); + } + + /** + * A sensible definition of {@link #floor} in terms of the {@code descendingIterator} method of + * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may + * wish to override {@link #floor} to forward to this implementation. + */ + protected E standardFloor(E e) { + return Iterators.getNext(headSet(e, true).descendingIterator(), null); + } + + @Override + public E ceiling(E e) { + return delegate().ceiling(e); + } + + /** + * A sensible definition of {@link #ceiling} in terms of the {@code iterator} method of + * {@link #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may + * wish to override {@link #ceiling} to forward to this implementation. + */ + protected E standardCeiling(E e) { + return Iterators.getNext(tailSet(e, true).iterator(), null); + } + + @Override + public E higher(E e) { + return delegate().higher(e); + } + + /** + * A sensible definition of {@link #higher} in terms of the {@code iterator} method of + * {@link #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may + * wish to override {@link #higher} to forward to this implementation. + */ + protected E standardHigher(E e) { + return Iterators.getNext(tailSet(e, false).iterator(), null); + } + + @Override + public E pollFirst() { + return delegate().pollFirst(); + } + + /** + * A sensible definition of {@link #pollFirst} in terms of the {@code iterator} method. If you + * override {@link #iterator} you may wish to override {@link #pollFirst} to forward to this + * implementation. + */ + protected E standardPollFirst() { + return poll(iterator()); + } + + @Override + public E pollLast() { + return delegate().pollLast(); + } + + /** + * A sensible definition of {@link #pollLast} in terms of the {@code descendingIterator} method. + * If you override {@link #descendingIterator} you may wish to override {@link #pollLast} to + * forward to this implementation. + */ + protected E standardPollLast() { + return poll(delegate().descendingIterator()); + } + + protected E standardFirst() { + return iterator().next(); + } + + protected E standardLast() { + return descendingIterator().next(); + } + + @Override + public NavigableSet descendingSet() { + return delegate().descendingSet(); + } + + /** + * A sensible implementation of {@link NavigableSet#descendingSet} in terms of the other methods + * of {@link NavigableSet}, notably including {@link NavigableSet#descendingIterator}. + * + *

In many cases, you may wish to override {@link ForwardingNavigableSet#descendingSet} to + * forward to this implementation or a subclass thereof. + * + * @since 12.0 + */ + @Beta + protected class StandardDescendingSet extends Sets.DescendingSet { + /** Constructor for use by subclasses. */ + public StandardDescendingSet() { + super(ForwardingNavigableSet.this); + } + } + + @Override + public Iterator descendingIterator() { + return delegate().descendingIterator(); + } + + @Override + public NavigableSet subSet( + E fromElement, + boolean fromInclusive, + E toElement, + boolean toInclusive) { + return delegate().subSet(fromElement, fromInclusive, toElement, toInclusive); + } + + /** + * A sensible definition of {@link #subSet(Object, boolean, Object, boolean)} in terms of the + * {@code headSet} and {@code tailSet} methods. In many cases, you may wish to override + * {@link #subSet(Object, boolean, Object, boolean)} to forward to this implementation. + */ + protected NavigableSet standardSubSet( + E fromElement, + boolean fromInclusive, + E toElement, + boolean toInclusive) { + return tailSet(fromElement, fromInclusive).headSet(toElement, toInclusive); + } + + /** + * A sensible definition of {@link #subSet(Object, Object)} in terms of the + * {@link #subSet(Object, boolean, Object, boolean)} method. If you override + * {@link #subSet(Object, boolean, Object, boolean)}, you may wish to override + * {@link #subSet(Object, Object)} to forward to this implementation. + */ + @Override + protected SortedSet standardSubSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return delegate().headSet(toElement, inclusive); + } + + /** + * A sensible definition of {@link #headSet(Object)} in terms of the + * {@link #headSet(Object, boolean)} method. If you override + * {@link #headSet(Object, boolean)}, you may wish to override + * {@link #headSet(Object)} to forward to this implementation. + */ + protected SortedSet standardHeadSet(E toElement) { + return headSet(toElement, false); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return delegate().tailSet(fromElement, inclusive); + } + + /** + * A sensible definition of {@link #tailSet(Object)} in terms of the + * {@link #tailSet(Object, boolean)} method. If you override + * {@link #tailSet(Object, boolean)}, you may wish to override + * {@link #tailSet(Object)} to forward to this implementation. + */ + protected SortedSet standardTailSet(E fromElement) { + return tailSet(fromElement, true); + } + + @Nullable + private E poll(Iterator iterator) { + if (iterator.hasNext()) { + E result = iterator.next(); + iterator.remove(); + return result; + } + return null; + } +} diff --git a/guava/src/com/google/common/collect/ForwardingObject.java b/guava/src/com/google/common/collect/ForwardingObject.java new file mode 100644 index 0000000..7fecdee --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingObject.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +/** + * An abstract base class for implementing the decorator pattern. + * The {@link #delegate()} method must be overridden to return the instance + * being decorated. + * + *

This class does not forward the {@code hashCode} and {@code equals} + * methods through to the backing object, but relies on {@code Object}'s + * implementation. This is necessary to preserve the symmetry of {@code equals}. + * Custom definitions of equality are usually based on an interface, such as + * {@code Set} or {@code List}, so that the implementation of {@code equals} can + * cast the object being tested for equality to the custom interface. {@code + * ForwardingObject} implements no such custom interfaces directly; they + * are implemented only in subclasses. Therefore, forwarding {@code equals} + * would break symmetry, as the forwarding object might consider itself equal to + * the object being tested, but the reverse could not be true. This behavior is + * consistent with the JDK's collection wrappers, such as + * {@link java.util.Collections#unmodifiableCollection}. Use an + * interface-specific subclass of {@code ForwardingObject}, such as {@link + * ForwardingList}, to preserve equality behavior, or override {@code equals} + * directly. + * + *

The {@code toString} method is forwarded to the delegate. Although this + * class does not implement {@link Serializable}, a serializable subclass may be + * created since this class has a parameter-less constructor. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingObject { + + /** Constructor for use by subclasses. */ + protected ForwardingObject() {} + + /** + * Returns the backing delegate instance that methods are forwarded to. + * Abstract subclasses generally override this method with an abstract method + * that has a more specific return type, such as {@link + * ForwardingSet#delegate}. Concrete subclasses override this method to supply + * the instance being decorated. + */ + protected abstract Object delegate(); + + /** + * Returns the string representation generated by the delegate's + * {@code toString} method. + */ + @Override public String toString() { + return delegate().toString(); + } + + /* No equals or hashCode. See class comments for details. */ +} diff --git a/guava/src/com/google/common/collect/ForwardingQueue.java b/guava/src/com/google/common/collect/ForwardingQueue.java new file mode 100644 index 0000000..3d30aaf --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingQueue.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * A queue which forwards all its method calls to another queue. Subclasses + * should override one or more methods to modify the behavior of the backing + * queue as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingQueue} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #offer} which can lead to unexpected behavior. In this case, you should + * override {@code offer} as well, either providing your own implementation, or + * delegating to the provided {@code standardOffer} method. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even + * when all of the methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingQueue extends ForwardingCollection + implements Queue { + + /** Constructor for use by subclasses. */ + protected ForwardingQueue() {} + + @Override protected abstract Queue delegate(); + + @Override + public boolean offer(E o) { + return delegate().offer(o); + } + + @Override + public E poll() { + return delegate().poll(); + } + + @Override + public E remove() { + return delegate().remove(); + } + + @Override + public E peek() { + return delegate().peek(); + } + + @Override + public E element() { + return delegate().element(); + } + + /** + * A sensible definition of {@link #offer} in terms of {@link #add}. If you + * override {@link #add}, you may wish to override {@link #offer} to forward + * to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardOffer(E e) { + try { + return add(e); + } catch (IllegalStateException caught) { + return false; + } + } + + /** + * A sensible definition of {@link #peek} in terms of {@link #element}. If you + * override {@link #element}, you may wish to override {@link #peek} to + * forward to this implementation. + * + * @since 7.0 + */ + @Beta protected E standardPeek() { + try { + return element(); + } catch (NoSuchElementException caught) { + return null; + } + } + + /** + * A sensible definition of {@link #poll} in terms of {@link #remove}. If you + * override {@link #remove}, you may wish to override {@link #poll} to forward + * to this implementation. + * + * @since 7.0 + */ + @Beta protected E standardPoll() { + try { + return remove(); + } catch (NoSuchElementException caught) { + return null; + } + } +} diff --git a/guava/src/com/google/common/collect/ForwardingSet.java b/guava/src/com/google/common/collect/ForwardingSet.java new file mode 100644 index 0000000..ef4b343 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingSet.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A set which forwards all its method calls to another set. Subclasses should + * override one or more methods to modify the behavior of the backing set as + * desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingSet} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardAddAll} method. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even + * when all of the methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingSet extends ForwardingCollection + implements Set { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingSet() {} + + @Override protected abstract Set delegate(); + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #removeAll} in terms of {@link #iterator} + * and {@link #remove}. If you override {@code iterator} or {@code remove}, + * you may wish to override {@link #removeAll} to forward to this + * implementation. + * + * @since 7.0 (this version overrides the {@code ForwardingCollection} version as of 12.0) + */ + @Override + protected boolean standardRemoveAll(Collection collection) { + return Sets.removeAllImpl(this, checkNotNull(collection)); // for GWT + } + + /** + * A sensible definition of {@link #equals} in terms of {@link #size} and + * {@link #containsAll}. If you override either of those methods, you may wish + * to override {@link #equals} to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected boolean standardEquals(@Nullable Object object) { + return Sets.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of {@link #iterator}. + * If you override {@link #iterator}, you may wish to override {@link #equals} + * to forward to this implementation. + * + * @since 7.0 + */ + @Beta protected int standardHashCode() { + return Sets.hashCodeImpl(this); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingSetMultimap.java b/guava/src/com/google/common/collect/ForwardingSetMultimap.java new file mode 100644 index 0000000..a923946 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingSetMultimap.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A set multimap which forwards all its method calls to another set multimap. + * Subclasses should override one or more methods to modify the behavior of + * the backing multimap as desired per the decorator pattern. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingSetMultimap + extends ForwardingMultimap implements SetMultimap { + + @Override protected abstract SetMultimap delegate(); + + @Override public Set> entries() { + return delegate().entries(); + } + + @Override public Set get(@Nullable K key) { + return delegate().get(key); + } + + @Override public Set removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + @Override public Set replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingSortedMap.java b/guava/src/com/google/common/collect/ForwardingSortedMap.java new file mode 100644 index 0000000..0808039 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingSortedMap.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedMap; + +import javax.annotation.Nullable; + +/** + * A sorted map which forwards all its method calls to another sorted map. + * Subclasses should override one or more methods to modify the behavior of + * the backing sorted map as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingSortedMap} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #put} alone will not change the behavior of {@link + * #putAll}, which can lead to unexpected behavior. In this case, you should + * override {@code putAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardPutAll} method. + * + *

Each of the {@code standard} methods, where appropriate, use the + * comparator of the map to test equality for both keys and values, unlike + * {@code ForwardingMap}. + * + *

The {@code standard} methods and the collection views they return are not + * guaranteed to be thread-safe, even when all of the methods that they depend + * on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingSortedMap extends ForwardingMap + implements SortedMap { + // TODO(user): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingSortedMap() {} + + @Override protected abstract SortedMap delegate(); + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + public K firstKey() { + return delegate().firstKey(); + } + + @Override + public SortedMap headMap(K toKey) { + return delegate().headMap(toKey); + } + + @Override + public K lastKey() { + return delegate().lastKey(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return delegate().subMap(fromKey, toKey); + } + + @Override + public SortedMap tailMap(K fromKey) { + return delegate().tailMap(fromKey); + } + + // unsafe, but worst case is a CCE is thrown, which callers will be expecting + @SuppressWarnings("unchecked") + private int unsafeCompare(Object k1, Object k2) { + Comparator comparator = comparator(); + if (comparator == null) { + return ((Comparable) k1).compareTo(k2); + } else { + return ((Comparator) comparator).compare(k1, k2); + } + } + + /** + * A sensible definition of {@link #containsKey} in terms of the {@code + * firstKey()} method of {@link #tailMap}. If you override {@link #tailMap}, + * you may wish to override {@link #containsKey} to forward to this + * implementation. + * + * @since 7.0 + */ + @Override @Beta protected boolean standardContainsKey(@Nullable Object key) { + try { + // any CCE will be caught + @SuppressWarnings("unchecked") + SortedMap self = (SortedMap) this; + Object ceilingKey = self.tailMap(key).firstKey(); + return unsafeCompare(ceilingKey, key) == 0; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; + } catch (NullPointerException e) { + return false; + } + } + + /** + * A sensible definition of {@link #remove} in terms of the {@code + * iterator()} of the {@code entrySet()} of {@link #tailMap}. If you override + * {@link #tailMap}, you may wish to override {@link #remove} to forward + * to this implementation. + * + * @since 7.0 + */ + @Override @Beta protected V standardRemove(@Nullable Object key) { + try { + // any CCE will be caught + @SuppressWarnings("unchecked") + SortedMap self = (SortedMap) this; + Iterator> entryIterator = + self.tailMap(key).entrySet().iterator(); + if (entryIterator.hasNext()) { + Entry ceilingEntry = entryIterator.next(); + if (unsafeCompare(ceilingEntry.getKey(), key) == 0) { + V value = ceilingEntry.getValue(); + entryIterator.remove(); + return value; + } + } + } catch (ClassCastException e) { + return null; + } catch (NullPointerException e) { + return null; + } + return null; + } + + /** + * A sensible default implementation of {@link #subMap(Object, Object)} in + * terms of {@link #headMap(Object)} and {@link #tailMap(Object)}. In some + * situations, you may wish to override {@link #subMap(Object, Object)} to + * forward to this implementation. + * + * @since 7.0 + */ + @Beta protected SortedMap standardSubMap(K fromKey, K toKey) { + return tailMap(fromKey).headMap(toKey); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingSortedSet.java b/guava/src/com/google/common/collect/ForwardingSortedSet.java new file mode 100644 index 0000000..756d9af --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingSortedSet.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A sorted set which forwards all its method calls to another sorted set. + * Subclasses should override one or more methods to modify the behavior of the + * backing sorted set as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingSortedSet} forward + * indiscriminately to the methods of the delegate. For example, + * overriding {@link #add} alone will not change the behavior of {@link + * #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or + * delegating to the provided {@code standardAddAll} method. + * + *

Each of the {@code standard} methods, where appropriate, uses the set's + * comparator (or the natural ordering of the elements, if there is no + * comparator) to test element equality. As a result, if the comparator is not + * consistent with equals, some of the standard implementations may violate the + * {@code Set} contract. + * + *

The {@code standard} methods and the collection views they return are not + * guaranteed to be thread-safe, even when all of the methods that they depend + * on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class ForwardingSortedSet extends ForwardingSet + implements SortedSet { + + /** Constructor for use by subclasses. */ + protected ForwardingSortedSet() {} + + @Override protected abstract SortedSet delegate(); + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + public E first() { + return delegate().first(); + } + + @Override + public SortedSet headSet(E toElement) { + return delegate().headSet(toElement); + } + + @Override + public E last() { + return delegate().last(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return delegate().subSet(fromElement, toElement); + } + + @Override + public SortedSet tailSet(E fromElement) { + return delegate().tailSet(fromElement); + } + + // unsafe, but worst case is a CCE is thrown, which callers will be expecting + @SuppressWarnings("unchecked") + private int unsafeCompare(Object o1, Object o2) { + Comparator comparator = comparator(); + return (comparator == null) + ? ((Comparable) o1).compareTo(o2) + : ((Comparator) comparator).compare(o1, o2); + } + + /** + * A sensible definition of {@link #contains} in terms of the {@code first()} + * method of {@link #tailSet}. If you override {@link #tailSet}, you may wish + * to override {@link #contains} to forward to this implementation. + * + * @since 7.0 + */ + @Override @Beta protected boolean standardContains(@Nullable Object object) { + try { + // any ClassCastExceptions are caught + @SuppressWarnings("unchecked") + SortedSet self = (SortedSet) this; + Object ceiling = self.tailSet(object).first(); + return unsafeCompare(ceiling, object) == 0; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; + } catch (NullPointerException e) { + return false; + } + } + + /** + * A sensible definition of {@link #remove} in terms of the {@code iterator()} + * method of {@link #tailSet}. If you override {@link #tailSet}, you may wish + * to override {@link #remove} to forward to this implementation. + * + * @since 7.0 + */ + @Override @Beta protected boolean standardRemove(@Nullable Object object) { + try { + // any ClassCastExceptions are caught + @SuppressWarnings("unchecked") + SortedSet self = (SortedSet) this; + Iterator iterator = self.tailSet(object).iterator(); + if (iterator.hasNext()) { + Object ceiling = iterator.next(); + if (unsafeCompare(ceiling, object) == 0) { + iterator.remove(); + return true; + } + } + } catch (ClassCastException e) { + return false; + } catch (NullPointerException e) { + return false; + } + return false; + } + + /** + * A sensible default implementation of {@link #subSet(Object, Object)} in + * terms of {@link #headSet(Object)} and {@link #tailSet(Object)}. In some + * situations, you may wish to override {@link #subSet(Object, Object)} to + * forward to this implementation. + * + * @since 7.0 + */ + @Beta protected SortedSet standardSubSet(E fromElement, E toElement) { + return tailSet(fromElement).headSet(toElement); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java b/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java new file mode 100644 index 0000000..d90c305 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A sorted set multimap which forwards all its method calls to another sorted + * set multimap. Subclasses should override one or more methods to modify the + * behavior of the backing multimap as desired per the decorator pattern. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingSortedSetMultimap + extends ForwardingSetMultimap implements SortedSetMultimap { + + /** Constructor for use by subclasses. */ + protected ForwardingSortedSetMultimap() {} + + @Override protected abstract SortedSetMultimap delegate(); + + @Override public SortedSet get(@Nullable K key) { + return delegate().get(key); + } + + @Override public SortedSet removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + @Override public SortedSet replaceValues( + K key, Iterable values) { + return delegate().replaceValues(key, values); + } + + @Override public Comparator valueComparator() { + return delegate().valueComparator(); + } +} diff --git a/guava/src/com/google/common/collect/ForwardingTable.java b/guava/src/com/google/common/collect/ForwardingTable.java new file mode 100644 index 0000000..92cc876 --- /dev/null +++ b/guava/src/com/google/common/collect/ForwardingTable.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A table which forwards all its method calls to another table. Subclasses + * should override one or more methods to modify the behavior of the backing + * map as desired per the decorator pattern. + * + * @author Gregory Kick + * @since 7.0 + */ +@GwtCompatible +public abstract class ForwardingTable extends ForwardingObject + implements Table { + /** Constructor for use by subclasses. */ + protected ForwardingTable() {} + + @Override protected abstract Table delegate(); + + @Override + public Set> cellSet() { + return delegate().cellSet(); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public Map column(C columnKey) { + return delegate().column(columnKey); + } + + @Override + public Set columnKeySet() { + return delegate().columnKeySet(); + } + + @Override + public Map> columnMap() { + return delegate().columnMap(); + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return delegate().contains(rowKey, columnKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + return delegate().containsColumn(columnKey); + } + + @Override + public boolean containsRow(Object rowKey) { + return delegate().containsRow(rowKey); + } + + @Override + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + return delegate().get(rowKey, columnKey); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public V put(R rowKey, C columnKey, V value) { + return delegate().put(rowKey, columnKey, value); + } + + @Override + public void putAll(Table table) { + delegate().putAll(table); + } + + @Override + public V remove(Object rowKey, Object columnKey) { + return delegate().remove(rowKey, columnKey); + } + + @Override + public Map row(R rowKey) { + return delegate().row(rowKey); + } + + @Override + public Set rowKeySet() { + return delegate().rowKeySet(); + } + + @Override + public Map> rowMap() { + return delegate().rowMap(); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override public boolean equals(Object obj) { + return (obj == this) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/guava/src/com/google/common/collect/GeneralRange.java b/guava/src/com/google/common/collect/GeneralRange.java new file mode 100644 index 0000000..c083d83 --- /dev/null +++ b/guava/src/com/google/common/collect/GeneralRange.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.BoundType.CLOSED; +import static com.google.common.collect.BoundType.OPEN; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.io.Serializable; +import java.util.Comparator; + +import javax.annotation.Nullable; + +/** + * A generalized interval on any ordering, for internal use. Supports {@code null}. Unlike + * {@link Range}, this allows the use of an arbitrary comparator. This is designed for use in the + * implementation of subcollections of sorted collection types. + * + *

Whenever possible, use {@code Range} instead, which is better supported. + * + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true) +final class GeneralRange implements Serializable { + /** + * Converts a Range to a GeneralRange. + */ + static GeneralRange from(Range range) { + @Nullable + T lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() : null; + BoundType lowerBoundType = range.hasLowerBound() ? range.lowerBoundType() : OPEN; + + @Nullable + T upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() : null; + BoundType upperBoundType = range.hasUpperBound() ? range.upperBoundType() : OPEN; + return new GeneralRange(Ordering.natural(), range.hasLowerBound(), lowerEndpoint, + lowerBoundType, range.hasUpperBound(), upperEndpoint, upperBoundType); + } + + /** + * Returns the whole range relative to the specified comparator. + */ + static GeneralRange all(Comparator comparator) { + return new GeneralRange(comparator, false, null, OPEN, false, null, OPEN); + } + + /** + * Returns everything above the endpoint relative to the specified comparator, with the specified + * endpoint behavior. + */ + static GeneralRange downTo(Comparator comparator, @Nullable T endpoint, + BoundType boundType) { + return new GeneralRange(comparator, true, endpoint, boundType, false, null, OPEN); + } + + /** + * Returns everything below the endpoint relative to the specified comparator, with the specified + * endpoint behavior. + */ + static GeneralRange upTo(Comparator comparator, @Nullable T endpoint, + BoundType boundType) { + return new GeneralRange(comparator, false, null, OPEN, true, endpoint, boundType); + } + + /** + * Returns everything between the endpoints relative to the specified comparator, with the + * specified endpoint behavior. + */ + static GeneralRange range(Comparator comparator, @Nullable T lower, + BoundType lowerType, @Nullable T upper, BoundType upperType) { + return new GeneralRange(comparator, true, lower, lowerType, true, upper, upperType); + } + + private final Comparator comparator; + private final boolean hasLowerBound; + @Nullable + private final T lowerEndpoint; + private final BoundType lowerBoundType; + private final boolean hasUpperBound; + @Nullable + private final T upperEndpoint; + private final BoundType upperBoundType; + + private GeneralRange(Comparator comparator, boolean hasLowerBound, + @Nullable T lowerEndpoint, BoundType lowerBoundType, boolean hasUpperBound, + @Nullable T upperEndpoint, BoundType upperBoundType) { + this.comparator = checkNotNull(comparator); + this.hasLowerBound = hasLowerBound; + this.hasUpperBound = hasUpperBound; + this.lowerEndpoint = lowerEndpoint; + this.lowerBoundType = checkNotNull(lowerBoundType); + this.upperEndpoint = upperEndpoint; + this.upperBoundType = checkNotNull(upperBoundType); + + if (hasLowerBound) { + comparator.compare(lowerEndpoint, lowerEndpoint); + } + if (hasUpperBound) { + comparator.compare(upperEndpoint, upperEndpoint); + } + if (hasLowerBound && hasUpperBound) { + int cmp = comparator.compare(lowerEndpoint, upperEndpoint); + // be consistent with Range + checkArgument(cmp <= 0, "lowerEndpoint (%s) > upperEndpoint (%s)", lowerEndpoint, + upperEndpoint); + if (cmp == 0) { + checkArgument(lowerBoundType != OPEN | upperBoundType != OPEN); + } + } + } + + Comparator comparator() { + return comparator; + } + + boolean hasLowerBound() { + return hasLowerBound; + } + + boolean hasUpperBound() { + return hasUpperBound; + } + + boolean isEmpty() { + return (hasUpperBound() && tooLow(getUpperEndpoint())) + || (hasLowerBound() && tooHigh(getLowerEndpoint())); + } + + boolean tooLow(@Nullable T t) { + if (!hasLowerBound()) { + return false; + } + T lbound = getLowerEndpoint(); + int cmp = comparator.compare(t, lbound); + return cmp < 0 | (cmp == 0 & getLowerBoundType() == OPEN); + } + + boolean tooHigh(@Nullable T t) { + if (!hasUpperBound()) { + return false; + } + T ubound = getUpperEndpoint(); + int cmp = comparator.compare(t, ubound); + return cmp > 0 | (cmp == 0 & getUpperBoundType() == OPEN); + } + + boolean contains(@Nullable T t) { + return !tooLow(t) && !tooHigh(t); + } + + /** + * Returns the intersection of the two ranges, or an empty range if their intersection is empty. + */ + GeneralRange intersect(GeneralRange other) { + checkNotNull(other); + checkArgument(comparator.equals(other.comparator)); + + boolean hasLowBound = this.hasLowerBound; + @Nullable + T lowEnd = getLowerEndpoint(); + BoundType lowType = getLowerBoundType(); + if (!hasLowerBound()) { + hasLowBound = other.hasLowerBound; + lowEnd = other.getLowerEndpoint(); + lowType = other.getLowerBoundType(); + } else if (other.hasLowerBound()) { + int cmp = comparator.compare(getLowerEndpoint(), other.getLowerEndpoint()); + if (cmp < 0 || (cmp == 0 && other.getLowerBoundType() == OPEN)) { + lowEnd = other.getLowerEndpoint(); + lowType = other.getLowerBoundType(); + } + } + + boolean hasUpBound = this.hasUpperBound; + @Nullable + T upEnd = getUpperEndpoint(); + BoundType upType = getUpperBoundType(); + if (!hasUpperBound()) { + hasUpBound = other.hasUpperBound; + upEnd = other.getUpperEndpoint(); + upType = other.getUpperBoundType(); + } else if (other.hasUpperBound()) { + int cmp = comparator.compare(getUpperEndpoint(), other.getUpperEndpoint()); + if (cmp > 0 || (cmp == 0 && other.getUpperBoundType() == OPEN)) { + upEnd = other.getUpperEndpoint(); + upType = other.getUpperBoundType(); + } + } + + if (hasLowBound && hasUpBound) { + int cmp = comparator.compare(lowEnd, upEnd); + if (cmp > 0 || (cmp == 0 && lowType == OPEN && upType == OPEN)) { + // force allowed empty range + lowEnd = upEnd; + lowType = OPEN; + upType = CLOSED; + } + } + + return new GeneralRange(comparator, hasLowBound, lowEnd, lowType, hasUpBound, upEnd, upType); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof GeneralRange) { + GeneralRange r = (GeneralRange) obj; + return comparator.equals(r.comparator) && hasLowerBound == r.hasLowerBound + && hasUpperBound == r.hasUpperBound && getLowerBoundType().equals(r.getLowerBoundType()) + && getUpperBoundType().equals(r.getUpperBoundType()) + && Objects.equal(getLowerEndpoint(), r.getLowerEndpoint()) + && Objects.equal(getUpperEndpoint(), r.getUpperEndpoint()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(comparator, getLowerEndpoint(), getLowerBoundType(), getUpperEndpoint(), + getUpperBoundType()); + } + + private transient GeneralRange reverse; + + /** + * Returns the same range relative to the reversed comparator. + */ + GeneralRange reverse() { + GeneralRange result = reverse; + if (result == null) { + result = new GeneralRange( + Ordering.from(comparator).reverse(), hasUpperBound, getUpperEndpoint(), + getUpperBoundType(), hasLowerBound, getLowerEndpoint(), getLowerBoundType()); + result.reverse = this; + return this.reverse = result; + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(comparator).append(":"); + switch (getLowerBoundType()) { + case CLOSED: + builder.append('['); + break; + case OPEN: + builder.append('('); + break; + } + if (hasLowerBound()) { + builder.append(getLowerEndpoint()); + } else { + builder.append("-\u221e"); + } + builder.append(','); + if (hasUpperBound()) { + builder.append(getUpperEndpoint()); + } else { + builder.append("\u221e"); + } + switch (getUpperBoundType()) { + case CLOSED: + builder.append(']'); + break; + case OPEN: + builder.append(')'); + break; + } + return builder.toString(); + } + + T getLowerEndpoint() { + return lowerEndpoint; + } + + BoundType getLowerBoundType() { + return lowerBoundType; + } + + T getUpperEndpoint() { + return upperEndpoint; + } + + BoundType getUpperBoundType() { + return upperBoundType; + } +} diff --git a/guava/src/com/google/common/collect/GenericMapMaker.java b/guava/src/com/google/common/collect/GenericMapMaker.java new file mode 100644 index 0000000..65bfa15 --- /dev/null +++ b/guava/src/com/google/common/collect/GenericMapMaker.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.collect.MapMaker.RemovalListener; +import com.google.common.collect.MapMaker.RemovalNotification; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * A class exactly like {@link MapMaker}, except restricted in the types of maps it can build. + * For the most part, you should probably just ignore the existence of this class. + * + * @param the base type for all key types of maps built by this map maker + * @param the base type for all value types of maps built by this map maker + * @author Kevin Bourrillion + * @since 7.0 + */ +@Beta +@GwtCompatible(emulated = true) +public abstract class GenericMapMaker { + @GwtIncompatible("To be supported") + enum NullListener implements RemovalListener { + INSTANCE; + + @Override + public void onRemoval(RemovalNotification notification) {} + } + + // Set by MapMaker, but sits in this class to preserve the type relationship + @GwtIncompatible("To be supported") + RemovalListener removalListener; + + // No subclasses but our own + GenericMapMaker() {} + + /** + * See {@link MapMaker#keyEquivalence}. + */ + @GwtIncompatible("To be supported") + abstract GenericMapMaker keyEquivalence(Equivalence equivalence); + + /** + * See {@link MapMaker#initialCapacity}. + */ + public abstract GenericMapMaker initialCapacity(int initialCapacity); + + /** + * See {@link MapMaker#maximumSize}. + */ + abstract GenericMapMaker maximumSize(int maximumSize); + + /** + * See {@link MapMaker#concurrencyLevel}. + */ + public abstract GenericMapMaker concurrencyLevel(int concurrencyLevel); + + /** + * See {@link MapMaker#weakKeys}. + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public abstract GenericMapMaker weakKeys(); + + /** + * See {@link MapMaker#softKeys}. + */ + @Deprecated + @GwtIncompatible("java.lang.ref.SoftReference") + public abstract GenericMapMaker softKeys(); + + /** + * See {@link MapMaker#weakValues}. + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public abstract GenericMapMaker weakValues(); + + /** + * See {@link MapMaker#softValues}. + */ + @GwtIncompatible("java.lang.ref.SoftReference") + public abstract GenericMapMaker softValues(); + + /** + * See {@link MapMaker#expireAfterWrite}. + */ + abstract GenericMapMaker expireAfterWrite(long duration, TimeUnit unit); + + /** + * See {@link MapMaker#expireAfterAccess}. + */ + @GwtIncompatible("To be supported") + abstract GenericMapMaker expireAfterAccess(long duration, TimeUnit unit); + + /* + * Note that MapMaker's removalListener() is not here, because once you're interacting with a + * GenericMapMaker you've already called that, and shouldn't be calling it again. + */ + + @SuppressWarnings("unchecked") // safe covariant cast + @GwtIncompatible("To be supported") + RemovalListener getRemovalListener() { + return (RemovalListener) Objects.firstNonNull(removalListener, NullListener.INSTANCE); + } + + /** + * See {@link MapMaker#makeMap}. + */ + public abstract ConcurrentMap makeMap(); + + /** + * See {@link MapMaker#makeCustomMap}. + */ + @GwtIncompatible("MapMakerInternalMap") + abstract MapMakerInternalMap makeCustomMap(); + + /** + * See {@link MapMaker#makeComputingMap}. + */ + @Deprecated + public abstract ConcurrentMap makeComputingMap( + Function computingFunction); +} diff --git a/guava/src/com/google/common/collect/GwtTransient.java b/guava/src/com/google/common/collect/GwtTransient.java new file mode 100644 index 0000000..a9a15e9 --- /dev/null +++ b/guava/src/com/google/common/collect/GwtTransient.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.annotations.GwtCompatible; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Private replacement for {@link com.google.gwt.user.client.rpc.GwtTransient} + * to work around build-system quirks. This annotation should be used + * only in {@code com.google.common.collect}. + */ +@Documented +@GwtCompatible +@Retention(RUNTIME) +@Target(FIELD) +@interface GwtTransient { +} diff --git a/guava/src/com/google/common/collect/HashBasedTable.java b/guava/src/com/google/common/collect/HashBasedTable.java new file mode 100644 index 0000000..4944174 --- /dev/null +++ b/guava/src/com/google/common/collect/HashBasedTable.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link Table} using hash tables. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link + * #columnMap()} have iterators that don't support {@code remove()}. Otherwise, + * all optional operations are supported. Null row keys, columns keys, and + * values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because + * the data is stored in a {@code Map>}. A method call like {@code + * column(columnKey).get(rowKey)} still runs quickly, since the row key is + * provided. However, {@code column(columnKey).size()} takes longer, since an + * iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads + * access this table concurrently and one of the threads modifies the table, it + * must be synchronized externally. + * + *

See the Guava User Guide article on + * {@code Table}. + * + * @author Jared Levy + * @since 7.0 + */ +@GwtCompatible(serializable = true) +public class HashBasedTable extends StandardTable { + private static class Factory + implements Supplier>, Serializable { + final int expectedSize; + Factory(int expectedSize) { + this.expectedSize = expectedSize; + } + @Override + public Map get() { + return Maps.newHashMapWithExpectedSize(expectedSize); + } + private static final long serialVersionUID = 0; + } + + /** + * Creates an empty {@code HashBasedTable}. + */ + public static HashBasedTable create() { + return new HashBasedTable( + new HashMap>(), new Factory(0)); + } + + /** + * Creates an empty {@code HashBasedTable} with the specified map sizes. + * + * @param expectedRows the expected number of distinct row keys + * @param expectedCellsPerRow the expected number of column key / value + * mappings in each row + * @throws IllegalArgumentException if {@code expectedRows} or {@code + * expectedCellsPerRow} is negative + */ + public static HashBasedTable create( + int expectedRows, int expectedCellsPerRow) { + checkArgument(expectedCellsPerRow >= 0); + Map> backingMap = + Maps.newHashMapWithExpectedSize(expectedRows); + return new HashBasedTable( + backingMap, new Factory(expectedCellsPerRow)); + } + + /** + * Creates a {@code HashBasedTable} with the same mappings as the specified + * table. + * + * @param table the table to copy + * @throws NullPointerException if any of the row keys, column keys, or values + * in {@code table} is null + */ + public static HashBasedTable create( + Table table) { + HashBasedTable result = create(); + result.putAll(table); + return result; + } + + HashBasedTable(Map> backingMap, Factory factory) { + super(backingMap, factory); + } + + // Overriding so NullPointerTester test passes. + + @Override public boolean contains( + @Nullable Object rowKey, @Nullable Object columnKey) { + return super.contains(rowKey, columnKey); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return super.containsColumn(columnKey); + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return super.containsRow(rowKey); + } + + @Override public boolean containsValue(@Nullable Object value) { + return super.containsValue(value); + } + + @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + return super.get(rowKey, columnKey); + } + + @Override public boolean equals(@Nullable Object obj) { + return super.equals(obj); + } + + @Override public V remove( + @Nullable Object rowKey, @Nullable Object columnKey) { + return super.remove(rowKey, columnKey); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/HashBiMap.java b/guava/src/com/google/common/collect/HashBiMap.java new file mode 100644 index 0000000..3b7a3ca --- /dev/null +++ b/guava/src/com/google/common/collect/HashBiMap.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@link BiMap} backed by two {@link HashMap} instances. This implementation + * allows null keys and values. A {@code HashBiMap} and its inverse are both + * serializable. + * + *

See the Guava User Guide article on + * {@code BiMap}. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class HashBiMap extends AbstractBiMap { + + /** + * Returns a new, empty {@code HashBiMap} with the default initial capacity + * (16). + */ + public static HashBiMap create() { + return new HashBiMap(); + } + + /** + * Constructs a new, empty bimap with the specified expected size. + * + * @param expectedSize the expected number of entries + * @throws IllegalArgumentException if the specified expected size is + * negative + */ + public static HashBiMap create(int expectedSize) { + return new HashBiMap(expectedSize); + } + + /** + * Constructs a new bimap containing initial values from {@code map}. The + * bimap is created with an initial capacity sufficient to hold the mappings + * in the specified map. + */ + public static HashBiMap create( + Map map) { + HashBiMap bimap = create(map.size()); + bimap.putAll(map); + return bimap; + } + + private HashBiMap() { + super(new HashMap(), new HashMap()); + } + + private HashBiMap(int expectedSize) { + super( + Maps.newHashMapWithExpectedSize(expectedSize), + Maps.newHashMapWithExpectedSize(expectedSize)); + } + + // Override these two methods to show that keys and values may be null + + @Override public V put(@Nullable K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(@Nullable K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** + * @serialData the number of entries, first key, first value, second key, + * second value, and so on. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMap(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int size = Serialization.readCount(stream); + setDelegates(Maps.newHashMapWithExpectedSize(size), + Maps.newHashMapWithExpectedSize(size)); + Serialization.populateMap(this, stream, size); + } + + @GwtIncompatible("Not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/HashMultimap.java b/guava/src/com/google/common/collect/HashMultimap.java new file mode 100644 index 0000000..bab2a05 --- /dev/null +++ b/guava/src/com/google/common/collect/HashMultimap.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of {@link Multimap} using hash tables. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public final class HashMultimap extends AbstractSetMultimap { + private static final int DEFAULT_VALUES_PER_KEY = 2; + + @VisibleForTesting + transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + + /** + * Creates a new, empty {@code HashMultimap} with the default initial + * capacities. + */ + public static HashMultimap create() { + return new HashMultimap(); + } + + /** + * Constructs an empty {@code HashMultimap} with enough capacity to hold the + * specified numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static HashMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new HashMultimap(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs a {@code HashMultimap} with the same mappings as the specified + * multimap. If a key-value mapping appears multiple times in the input + * multimap, it only appears once in the constructed multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static HashMultimap create( + Multimap multimap) { + return new HashMultimap(multimap); + } + + private HashMultimap() { + super(new HashMap>()); + } + + private HashMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Maps.>newHashMapWithExpectedSize(expectedKeys)); + Preconditions.checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private HashMultimap(Multimap multimap) { + super(Maps.>newHashMapWithExpectedSize( + multimap.keySet().size())); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code HashSet} for a collection of values for one key. + * + * @return a new {@code HashSet} containing a collection of values for one key + */ + @Override Set createCollection() { + return Sets.newHashSetWithExpectedSize(expectedValuesPerKey); + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for + * each distinct key: the key, number of values for that key, and the + * key's values + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(expectedValuesPerKey); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = stream.readInt(); + int distinctKeys = Serialization.readCount(stream); + Map> map = Maps.newHashMapWithExpectedSize(distinctKeys); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + @GwtIncompatible("Not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/HashMultiset.java b/guava/src/com/google/common/collect/HashMultiset.java new file mode 100644 index 0000000..51cf7fb --- /dev/null +++ b/guava/src/com/google/common/collect/HashMultiset.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; + +/** + * Multiset implementation backed by a {@link HashMap}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public final class HashMultiset extends AbstractMapBasedMultiset { + + /** + * Creates a new, empty {@code HashMultiset} using the default initial + * capacity. + */ + public static HashMultiset create() { + return new HashMultiset(); + } + + /** + * Creates a new, empty {@code HashMultiset} with the specified expected + * number of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static HashMultiset create(int distinctElements) { + return new HashMultiset(distinctElements); + } + + /** + * Creates a new {@code HashMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself + * a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static HashMultiset create(Iterable elements) { + HashMultiset multiset = + create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private HashMultiset() { + super(new HashMap()); + } + + private HashMultiset(int distinctElements) { + super(Maps.newHashMapWithExpectedSize(distinctElements)); + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap( + Maps.newHashMapWithExpectedSize(distinctElements)); + Serialization.populateMultiset(this, stream, distinctElements); + } + + @GwtIncompatible("Not needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/Hashing.java b/guava/src/com/google/common/collect/Hashing.java new file mode 100644 index 0000000..9c5f6bc --- /dev/null +++ b/guava/src/com/google/common/collect/Hashing.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Static methods for implementing hash-based collections. + * + * @author Kevin Bourrillion + * @author Jesse Wilson + */ +@GwtCompatible +final class Hashing { + private Hashing() {} + + /* + * This method was written by Doug Lea with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + * + * As of 2010/06/11, this method is identical to the (package private) hash + * method in OpenJDK 7's java.util.HashMap class. + */ + static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableAsList.java b/guava/src/com/google/common/collect/ImmutableAsList.java new file mode 100644 index 0000000..249abee --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableAsList.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +/** + * List returned by {@link ImmutableCollection#asList} that delegates {@code contains} checks + * to the backing collection. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") +abstract class ImmutableAsList extends ImmutableList { + abstract ImmutableCollection delegateCollection(); + + @Override public boolean contains(Object target) { + // The collection's contains() is at least as fast as ImmutableList's + // and is often faster. + return delegateCollection().contains(target); + } + + @Override + public int size() { + return delegateCollection().size(); + } + + @Override + public boolean isEmpty() { + return delegateCollection().isEmpty(); + } + + @Override + boolean isPartialView() { + return delegateCollection().isPartialView(); + } + + /** + * Serialized form that leads to the same performance as the original list. + */ + @GwtIncompatible("serialization") + static class SerializedForm implements Serializable { + final ImmutableCollection collection; + SerializedForm(ImmutableCollection collection) { + this.collection = collection; + } + Object readResolve() { + return collection.asList(); + } + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("serialization") + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @GwtIncompatible("serialization") + @Override Object writeReplace() { + return new SerializedForm(delegateCollection()); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableBiMap.java b/guava/src/com/google/common/collect/ImmutableBiMap.java new file mode 100644 index 0000000..27ba968 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableBiMap.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable {@link BiMap} with reliable user-specified iteration order. Does + * not permit null keys or values. An {@code ImmutableBiMap} and its inverse + * have the same iteration ordering. + * + *

An instance of {@code ImmutableBiMap} contains its own data and will + * never change. {@code ImmutableBiMap} is convenient for + * {@code public static final} maps ("constant maps") and also lets you easily + * make a "defensive copy" of a bimap provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public abstract class ImmutableBiMap extends ImmutableMap + implements BiMap { + + /** + * Returns the empty bimap. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableBiMap of() { + return (ImmutableBiMap) EmptyImmutableBiMap.INSTANCE; + } + + /** + * Returns an immutable bimap containing a single entry. + */ + public static ImmutableBiMap of(K k1, V v1) { + return new RegularImmutableBiMap(ImmutableMap.of(k1, v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of(K k1, V v1, K k2, V v2) { + return new RegularImmutableBiMap(ImmutableMap.of(k1, v1, k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4, k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable bimap instances, especially {@code public + * static final} bimaps ("constant bimaps"). Example:

   {@code
+   *
+   *   static final ImmutableBiMap WORD_TO_INT =
+   *       new ImmutableBiMap.Builder()
+   *           .put("one", 1)
+   *           .put("two", 2)
+   *           .put("three", 3)
+   *           .build();}
+ * + * For small immutable bimaps, the {@code ImmutableBiMap.of()} methods + * are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple bimaps in series. Each bimap is a superset + * of the bimaps created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder extends ImmutableMap.Builder { + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableBiMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built bimap. Duplicate + * keys or values are not allowed, and will cause {@link #build} to fail. + */ + @Override public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Associates all of the given map's keys and values in the built bimap. + * Duplicate keys or values are not allowed, and will cause {@link #build} + * to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Returns a newly-created immutable bimap. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override public ImmutableBiMap build() { + ImmutableMap map = super.build(); + if (map.isEmpty()) { + return of(); + } + return new RegularImmutableBiMap(map); + } + } + + /** + * Returns an immutable bimap containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws IllegalArgumentException if two keys have the same value + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableBiMap copyOf( + Map map) { + if (map instanceof ImmutableBiMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableBiMap bimap = (ImmutableBiMap) map; + // TODO(user): if we need to make a copy of a BiMap because the + // forward map is a view, don't make a copy of the non-view delegate map + if (!bimap.isPartialView()) { + return bimap; + } + } + + if (map.isEmpty()) { + return of(); + } + + ImmutableMap immutableMap = ImmutableMap.copyOf(map); + return new RegularImmutableBiMap(immutableMap); + } + + ImmutableBiMap() {} + + abstract ImmutableMap delegate(); + + /** + * {@inheritDoc} + * + *

The inverse of an {@code ImmutableBiMap} is another + * {@code ImmutableBiMap}. + */ + @Override + public abstract ImmutableBiMap inverse(); + + @Override public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + @Override public boolean containsValue(@Nullable Object value) { + return inverse().containsKey(value); + } + + @Override ImmutableSet> createEntrySet() { + return delegate().entrySet(); + } + + @Override public V get(@Nullable Object key) { + return delegate().get(key); + } + + @Override public ImmutableSet keySet() { + return delegate().keySet(); + } + + /** + * Returns an immutable set of the values in this map. The values are in the + * same order as the parameters used to build this map. + */ + @Override public ImmutableSet values() { + return inverse().keySet(); + } + + /** + * Guaranteed to throw an exception and leave the bimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + @Override public String toString() { + return delegate().toString(); + } + + /** + * Serialized type for all ImmutableBiMap instances. It captures the logical + * contents and they are reconstructed using public factory methods. This + * ensures that the implementation types remain as implementation details. + * + * Since the bimap is immutable, ImmutableBiMap doesn't require special logic + * for keeping the bimap and its inverse in sync during serialization, the way + * AbstractBiMap does. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + SerializedForm(ImmutableBiMap bimap) { + super(bimap); + } + @Override Object readResolve() { + Builder builder = new Builder(); + return createMap(builder); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java b/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java new file mode 100644 index 0000000..1c596e2 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.primitives.Primitives; + +import java.util.Map; + +/** + * A class-to-instance map backed by an {@link ImmutableMap}. See also {@link + * MutableClassToInstanceMap}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +public final class ImmutableClassToInstanceMap extends + ForwardingMap, B> implements ClassToInstanceMap { + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable class-to-instance maps. Example: + *

   {@code
+   *
+   *   static final ImmutableClassToInstanceMap HANDLERS =
+   *       new ImmutableClassToInstanceMap.Builder()
+   *           .put(FooHandler.class, new FooHandler())
+   *           .put(BarHandler.class, new SubBarHandler())
+   *           .put(Handler.class, new QuuxHandler())
+   *           .build();}
+ * + * After invoking {@link #build()} it is still possible to add more entries + * and build again. Thus each map generated by this builder will be a superset + * of any map generated before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder { + private final ImmutableMap.Builder, B> mapBuilder + = ImmutableMap.builder(); + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(Class key, T value) { + mapBuilder.put(key, value); + return this; + } + + /** + * Associates all of {@code map's} keys and values in the built map. + * Duplicate keys are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type + * specified by its key + */ + public Builder putAll( + Map, ? extends T> map) { + for (Entry, ? extends T> entry + : map.entrySet()) { + Class type = entry.getKey(); + T value = entry.getValue(); + mapBuilder.put(type, cast(type, value)); + } + return this; + } + + private static T cast(Class type, B value) { + return Primitives.wrap(type).cast(value); + } + + /** + * Returns a new immutable class-to-instance map containing the entries + * provided to this builder. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableClassToInstanceMap build() { + return new ImmutableClassToInstanceMap(mapBuilder.build()); + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Note: Despite what the method name suggests, if {@code map} is + * an {@code ImmutableClassToInstanceMap}, no copy will actually be performed. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type + * specified by its key + */ + public static ImmutableClassToInstanceMap copyOf( + Map, ? extends S> map) { + if (map instanceof ImmutableClassToInstanceMap) { + @SuppressWarnings("unchecked") // covariant casts safe (unmodifiable) + // Eclipse won't compile if we cast to the parameterized type. + ImmutableClassToInstanceMap cast = (ImmutableClassToInstanceMap) map; + return cast; + } + return new Builder().putAll(map).build(); + } + + private final ImmutableMap, B> delegate; + + private ImmutableClassToInstanceMap( + ImmutableMap, B> delegate) { + this.delegate = delegate; + } + + @Override protected Map, B> delegate() { + return delegate; + } + + @Override + @SuppressWarnings("unchecked") // value could not get in if not a T + public T getInstance(Class type) { + return (T) delegate.get(type); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public T putInstance(Class type, T value) { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableCollection.java b/guava/src/com/google/common/collect/ImmutableCollection.java new file mode 100644 index 0000000..d7e37ee --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableCollection.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +import javax.annotation.Nullable; + +/** + * An immutable collection. Does not permit null elements. + * + *

In addition to the {@link Collection} methods, this class has an {@link + * #asList()} method, which returns a list view of the collection's elements. + * + *

Note: Although this class is not final, it cannot be subclassed + * outside of this package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + * @author Jesse Wilson + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableCollection + implements Collection, Serializable { + static final ImmutableCollection EMPTY_IMMUTABLE_COLLECTION + = new EmptyImmutableCollection(); + + ImmutableCollection() {} + + /** + * Returns an unmodifiable iterator across the elements in this collection. + */ + @Override + public abstract UnmodifiableIterator iterator(); + + @Override + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override + public T[] toArray(T[] other) { + return ObjectArrays.toArrayImpl(this, other); + } + + @Override + public boolean contains(@Nullable Object object) { + return object != null && Iterators.contains(iterator(), object); + } + + @Override + public boolean containsAll(Collection targets) { + return Collections2.containsAllImpl(this, targets); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override public String toString() { + return Collections2.toStringImpl(this); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean addAll(Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean removeAll(Collection oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean retainAll(Collection elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /* + * TODO(kevinb): Restructure code so ImmutableList doesn't contain this + * variable, which it doesn't use. + */ + private transient ImmutableList asList; + + /** + * Returns a list view of the collection. + * + * @since 2.0 + */ + public ImmutableList asList() { + ImmutableList list = asList; + return (list == null) ? (asList = createAsList()) : list; + } + + ImmutableList createAsList() { + switch (size()) { + case 0: + return ImmutableList.of(); + case 1: + return ImmutableList.of(iterator().next()); + default: + return new RegularImmutableAsList(this, toArray()); + } + } + + abstract boolean isPartialView(); + + private static class EmptyImmutableCollection + extends ImmutableCollection { + @Override + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(@Nullable Object object) { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.EMPTY_LIST_ITERATOR; + } + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @Override public Object[] toArray() { + return EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] array) { + if (array.length > 0) { + array[0] = null; + } + return array; + } + + @Override ImmutableList createAsList() { + return ImmutableList.of(); + } + + @Override boolean isPartialView() { + return false; + } + } + + /** + * Nonempty collection stored in an array. + */ + private static class ArrayImmutableCollection + extends ImmutableCollection { + private final E[] elements; + + ArrayImmutableCollection(E[] elements) { + this.elements = elements; + } + + @Override + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.forArray(elements); + } + + @Override ImmutableList createAsList() { + return elements.length == 1 ? new SingletonImmutableList(elements[0]) + : new RegularImmutableList(elements); + } + + @Override boolean isPartialView() { + return false; + } + } + + /* + * Serializes ImmutableCollections as their logical contents. This ensures + * that implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return elements.length == 0 + ? EMPTY_IMMUTABLE_COLLECTION + : new ArrayImmutableCollection(Platform.clone(elements)); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Abstract base class for builders of {@link ImmutableCollection} types. + * + * @since 10.0 + */ + public abstract static class Builder { + static final int DEFAULT_INITIAL_CAPACITY = 4; + + @VisibleForTesting + static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity + } + return newCapacity; + } + + Builder() { + } + + /** + * Adds {@code element} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class covariantly returns its own type from + * this method. + * + * @param element the element to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code element} is null + */ + public abstract Builder add(E element); + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder add(E... elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder addAll(Iterable elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder addAll(Iterator elements) { + while (elements.hasNext()) { + add(elements.next()); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableCollection} of the appropriate + * type, containing the elements provided to this builder. + * + *

Note that each builder class covariantly returns the appropriate type + * of {@code ImmutableCollection} from this method. + */ + public abstract ImmutableCollection build(); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableEntry.java b/guava/src/com/google/common/collect/ImmutableEntry.java new file mode 100644 index 0000000..3824d7a --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableEntry.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * @see com.google.common.collect.Maps#immutableEntry(Object, Object) + */ +@GwtCompatible(serializable = true) +class ImmutableEntry extends AbstractMapEntry + implements Serializable { + private final K key; + private final V value; + + ImmutableEntry(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Nullable @Override public K getKey() { + return key; + } + + @Nullable @Override public V getValue() { + return value; + } + + @Override public final V setValue(V value){ + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ImmutableEnumSet.java b/guava/src/com/google/common/collect/ImmutableEnumSet.java new file mode 100644 index 0000000..ac6dd0e --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableEnumSet.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Collection; +import java.util.EnumSet; + +/** + * Implementation of {@link ImmutableSet} backed by a non-empty {@link + * java.util.EnumSet}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +final class ImmutableEnumSet> extends ImmutableSet { + /* + * Notes on EnumSet and >: + * + * This class isn't an arbitrary ForwardingImmutableSet because we need to + * know that calling {@code clone()} during deserialization will return an + * object that no one else has a reference to, allowing us to guarantee + * immutability. Hence, we support only {@link EnumSet}. + */ + private final transient EnumSet delegate; + + ImmutableEnumSet(EnumSet delegate) { + this.delegate = delegate; + } + + @Override boolean isPartialView() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.unmodifiableIterator(delegate.iterator()); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override public boolean containsAll(Collection collection) { + return delegate.containsAll(collection); + } + + @Override public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override public Object[] toArray() { + return delegate.toArray(); + } + + @Override public T[] toArray(T[] array) { + return delegate.toArray(array); + } + + @Override public boolean equals(Object object) { + return object == this || delegate.equals(object); + } + + private transient int hashCode; + + @Override public int hashCode() { + int result = hashCode; + return (result == 0) ? hashCode = delegate.hashCode() : result; + } + + @Override public String toString() { + return delegate.toString(); + } + + // All callers of the constructor are restricted to >. + @Override Object writeReplace() { + return new EnumSerializedForm(delegate); + } + + /* + * This class is used to serialize ImmutableEnumSet instances. + */ + private static class EnumSerializedForm> + implements Serializable { + final EnumSet delegate; + EnumSerializedForm(EnumSet delegate) { + this.delegate = delegate; + } + Object readResolve() { + // EJ2 #76: Write readObject() methods defensively. + return new ImmutableEnumSet(delegate.clone()); + } + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/ImmutableList.java b/guava/src/com/google/common/collect/ImmutableList.java new file mode 100644 index 0000000..1791bef --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableList.java @@ -0,0 +1,749 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.collect.ObjectArrays.checkElementNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +import javax.annotation.Nullable; + +/** + * A high-performance, immutable, random-access {@code List} implementation. + * Does not permit null elements. + * + *

Unlike {@link Collections#unmodifiableList}, which is a view of a + * separate collection that can still change, an instance of {@code + * ImmutableList} contains its own private data and will never change. + * {@code ImmutableList} is convenient for {@code public static final} lists + * ("constant lists") and also lets you easily make a "defensive copy" of a list + * provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @see ImmutableMap + * @see ImmutableSet + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableList extends ImmutableCollection + implements List, RandomAccess { + /** + * Returns the empty immutable list. This set behaves and performs comparably + * to {@link Collections#emptyList}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the list will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableList of() { + return (ImmutableList) EmptyImmutableList.INSTANCE; + } + + /** + * Returns an immutable list containing a single element. This list behaves + * and performs comparably to {@link Collections#singleton}, but will not + * accept a null element. It is preferable mainly for consistency and + * maintainability of your code. + * + * @throws NullPointerException if {@code element} is null + */ + public static ImmutableList of(E element) { + return new SingletonImmutableList(element); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2) { + return construct(e1, e2); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3) { + return construct(e1, e2, e3); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4) { + return construct(e1, e2, e3, e4); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5) { + return construct(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6) { + return construct(e1, e2, e3, e4, e5, e6); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return construct(e1, e2, e3, e4, e5, e6, e7); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11); + } + + // These go up to eleven. After that, you just get the varargs form, and + // whatever warnings might come along with it. :( + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 3.0 (source-compatible since 2.0) + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, + E... others) { + Object[] array = new Object[12 + others.length]; + array[0] = e1; + array[1] = e2; + array[2] = e3; + array[3] = e4; + array[4] = e5; + array[5] = e6; + array[6] = e7; + array[7] = e8; + array[8] = e9; + array[9] = e10; + array[10] = e11; + array[11] = e12; + System.arraycopy(others, 0, array, 12, others.length); + return construct(array); + } + + /** + * Returns an immutable list containing the given elements, in order. If + * {@code elements} is a {@link Collection}, this method behaves exactly as + * {@link #copyOf(Collection)}; otherwise, it behaves exactly as {@code + * copyOf(elements.iterator()}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList copyOf(Iterable elements) { + checkNotNull(elements); // TODO(kevinb): is this here only for GWT? + return (elements instanceof Collection) + ? copyOf(Collections2.cast(elements)) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

Note that if {@code list} is a {@code List}, then {@code + * ImmutableList.copyOf(list)} returns an {@code ImmutableList} + * containing each of the strings in {@code list}, while + * ImmutableList.of(list)} returns an {@code ImmutableList>} + * containing one element (the given list itself). + * + *

This method is safe to use even when {@code elements} is a synchronized + * or concurrent collection that is currently being modified by another + * thread. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList copyOf(Collection elements) { + if (elements instanceof ImmutableCollection) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableList list = ((ImmutableCollection) elements).asList(); + return list.isPartialView() ? copyFromCollection(list) : list; + } + return copyFromCollection(elements); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but going further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new ImmutableList.Builder() + .add(first) + .addAll(elements) + .build(); + } + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 3.0 + */ + public static ImmutableList copyOf(E[] elements) { + switch (elements.length) { + case 0: + return ImmutableList.of(); + case 1: + return new SingletonImmutableList(elements[0]); + default: + return construct(elements.clone()); + } + } + + /** + * Views the array as an immutable list. The array must have only non-null {@code E} elements. + * + *

The array must be internally created. + */ + static ImmutableList asImmutableList(Object[] elements) { + switch (elements.length) { + case 0: + return of(); + case 1: + @SuppressWarnings("unchecked") // collection had only Es in it + ImmutableList list = new SingletonImmutableList((E) elements[0]); + return list; + default: + return construct(elements); + } + } + + private static ImmutableList copyFromCollection( + Collection collection) { + return asImmutableList(collection.toArray()); + } + + /** {@code elements} has to be internally created array. */ + private static ImmutableList construct(Object... elements) { + for (int i = 0; i < elements.length; i++) { + ObjectArrays.checkElementNotNull(elements[i], i); + } + return new RegularImmutableList(elements); + } + + ImmutableList() {} + + // This declaration is needed to make List.iterator() and + // ImmutableCollection.iterator() consistent. + @Override public UnmodifiableIterator iterator() { + return listIterator(); + } + + @Override public UnmodifiableListIterator listIterator() { + return listIterator(0); + } + + @Override public UnmodifiableListIterator listIterator(int index) { + return new AbstractIndexedListIterator(size(), index) { + @Override + protected E get(int index) { + return ImmutableList.this.get(index); + } + }; + } + + @Override + public int indexOf(@Nullable Object object) { + return (object == null) ? -1 : Lists.indexOfImpl(this, object); + } + + @Override + public int lastIndexOf(@Nullable Object object) { + return (object == null) ? -1 : Lists.lastIndexOfImpl(this, object); + } + + @Override + public boolean contains(@Nullable Object object) { + return indexOf(object) >= 0; + } + + // constrain the return type to ImmutableList + + /** + * Returns an immutable list of the elements between the specified {@code + * fromIndex}, inclusive, and {@code toIndex}, exclusive. (If {@code + * fromIndex} and {@code toIndex} are equal, the empty immutable list is + * returned.) + */ + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + int length = toIndex - fromIndex; + switch (length) { + case 0: + return of(); + case 1: + return of(get(fromIndex)); + default: + return subListUnchecked(fromIndex, toIndex); + } + } + + /** + * Called by the default implementation of {@link #subList} when {@code + * toIndex - fromIndex > 1}, after index validation has already been + * performed. + */ + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + return new SubList(fromIndex, toIndex - fromIndex); + } + + class SubList extends ImmutableList { + transient final int offset; + transient final int length; + + SubList(int offset, int length) { + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public E get(int index) { + checkElementIndex(index, length); + return ImmutableList.this.get(index + offset); + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, length); + return ImmutableList.this.subList(fromIndex + offset, toIndex + offset); + } + + @Override + boolean isPartialView() { + return true; + } + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean addAll(int index, Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final E remove(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Returns this list instance. + * + * @since 2.0 + */ + @Override public ImmutableList asList() { + return this; + } + + /** + * Returns a view of this immutable list in reverse order. For example, {@code + * ImmutableList.of(1, 2, 3).reverse()} is equivalent to {@code + * ImmutableList.of(3, 2, 1)}. + * + * @return a view of this immutable list in reverse order + * @since 7.0 + */ + public ImmutableList reverse() { + return new ReverseImmutableList(this); + } + + private static class ReverseImmutableList extends ImmutableList { + private final transient ImmutableList forwardList; + private final transient int size; + + ReverseImmutableList(ImmutableList backingList) { + this.forwardList = backingList; + this.size = backingList.size(); + } + + private int reverseIndex(int index) { + return (size - 1) - index; + } + + private int reversePosition(int index) { + return size - index; + } + + @Override public ImmutableList reverse() { + return forwardList; + } + + @Override public boolean contains(@Nullable Object object) { + return forwardList.contains(object); + } + + @Override public boolean containsAll(Collection targets) { + return forwardList.containsAll(targets); + } + + @Override public int indexOf(@Nullable Object object) { + int index = forwardList.lastIndexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override public int lastIndexOf(@Nullable Object object) { + int index = forwardList.indexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size); + return forwardList.subList( + reversePosition(toIndex), reversePosition(fromIndex)).reverse(); + } + + @Override public E get(int index) { + checkElementIndex(index, size); + return forwardList.get(reverseIndex(index)); + } + + @Override public UnmodifiableListIterator listIterator(int index) { + checkPositionIndex(index, size); + final UnmodifiableListIterator forward = + forwardList.listIterator(reversePosition(index)); + return new UnmodifiableListIterator() { + @Override public boolean hasNext() { + return forward.hasPrevious(); + } + + @Override public boolean hasPrevious() { + return forward.hasNext(); + } + + @Override public E next() { + return forward.previous(); + } + + @Override public int nextIndex() { + return reverseIndex(forward.previousIndex()); + } + + @Override public E previous() { + return forward.next(); + } + + @Override public int previousIndex() { + return reverseIndex(forward.nextIndex()); + } + }; + } + + @Override public int size() { + return size; + } + + @Override public boolean isEmpty() { + return forwardList.isEmpty(); + } + + @Override boolean isPartialView() { + return forwardList.isPartialView(); + } + } + + @Override public boolean equals(Object obj) { + return Lists.equalsImpl(this, obj); + } + + @Override public int hashCode() { + return Lists.hashCodeImpl(this); + } + + /* + * Serializes ImmutableLists as their logical contents. This ensures that + * implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return copyOf(elements); + } + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable list instances, especially {@code public + * static final} lists ("constant lists"). Example:

   {@code
+   *
+   *   public static final ImmutableList GOOGLE_COLORS
+   *       = new ImmutableList.Builder()
+   *           .addAll(WEBSAFE_COLORS)
+   *           .add(new Color(0, 191, 255))
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple lists in series. Each new list contains all the + * elements of the ones created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder extends ImmutableCollection.Builder { + private Object[] contents; + private int size; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableList#builder}. + */ + public Builder() { + this(DEFAULT_INITIAL_CAPACITY); + } + + // TODO(user): consider exposing this + Builder(int capacity) { + this.contents = new Object[capacity]; + this.size = 0; + } + + /** + * Expand capacity to allow the specified number of elements to be added. + */ + Builder expandFor(int count) { + int minCapacity = size + count; + if (contents.length < minCapacity) { + this.contents = ObjectArrays.arraysCopyOf( + this.contents, expandedCapacity(contents.length, minCapacity)); + } + return this; + } + + /** + * Adds {@code element} to the {@code ImmutableList}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + checkNotNull(element); + expandFor(1); + contents[size++] = element; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Collection) { + Collection collection = (Collection) elements; + expandFor(collection.size()); + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + for (int i = 0; i < elements.length; i++) { + checkElementNotNull(elements[i], i); + } + expandFor(elements.length); + System.arraycopy(elements, 0, contents, size, elements.length); + size += elements.length; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableList} based on the contents of + * the {@code Builder}. + */ + @Override public ImmutableList build() { + switch (size) { + case 0: + return of(); + case 1: + @SuppressWarnings("unchecked") // guaranteed to be an E + E singleElement = (E) contents[0]; + return of(singleElement); + default: + if (size == contents.length) { + // no need to copy; any further add operations on the builder will copy the buffer + return new RegularImmutableList(contents); + } else { + return new RegularImmutableList(ObjectArrays.arraysCopyOf(contents, size)); + } + } + } + } +} diff --git a/guava/src/com/google/common/collect/ImmutableListMultimap.java b/guava/src/com/google/common/collect/ImmutableListMultimap.java new file mode 100644 index 0000000..98db0ed --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableListMultimap.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * An immutable {@link ListMultimap} with reliable user-specified key and value + * iteration order. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableListMultimap(ListMultimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableListMultimap} contains its own data and will never + * change. {@code ImmutableListMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public class ImmutableListMultimap + extends ImmutableMultimap + implements ListMultimap { + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableListMultimap of() { + return (ImmutableListMultimap) EmptyImmutableListMultimap.INSTANCE; + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableListMultimap of(K k1, V v1) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable {@code ListMultimap} instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableListMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple multimaps in series. Each multimap contains the + * key-value mappings in the previously created multimaps. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder + extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableListMultimap#builder}. + */ + public Builder() {} + + @Override public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * {@inheritDoc} + * + * @since 11.0 + */ + @Override public Builder put( + Entry entry) { + super.put(entry); + return this; + } + + @Override public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + @Override public Builder putAll(K key, V... values) { + super.putAll(key, values); + return this; + } + + @Override public Builder putAll( + Multimap multimap) { + super.putAll(multimap); + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + @Beta @Override + public Builder orderKeysBy(Comparator keyComparator) { + super.orderKeysBy(keyComparator); + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + @Beta @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** + * Returns a newly-created immutable list multimap. + */ + @Override public ImmutableListMultimap build() { + return (ImmutableListMultimap) super.build(); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code + * multimap}. The generated multimap's key and value orderings correspond to + * the iteration ordering of the {@code multimap.asMap()} view. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableListMultimap copyOf( + Multimap multimap) { + if (multimap.isEmpty()) { + return of(); + } + + // TODO(user): copy ImmutableSetMultimap by using asList() on the sets + if (multimap instanceof ImmutableListMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableListMultimap kvMultimap + = (ImmutableListMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int size = 0; + + for (Entry> entry + : multimap.asMap().entrySet()) { + ImmutableList list = ImmutableList.copyOf(entry.getValue()); + if (!list.isEmpty()) { + builder.put(entry.getKey(), list); + size += list.size(); + } + } + + return new ImmutableListMultimap(builder.build(), size); + } + + ImmutableListMultimap(ImmutableMap> map, int size) { + super(map, size); + } + + // views + + /** + * Returns an immutable list of the values for the given key. If no mappings + * in the multimap have the provided key, an empty immutable list is + * returned. The values are in the same order as the parameters used to build + * this multimap. + */ + @Override public ImmutableList get(@Nullable K key) { + // This cast is safe as its type is known in constructor. + ImmutableList list = (ImmutableList) map.get(key); + return (list == null) ? ImmutableList.of() : list; + } + + private transient ImmutableListMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a list multimap can contain multiple pairs with + * the same key and value, this method returns an {@code + * ImmutableListMultimap} rather than the {@code ImmutableMultimap} specified + * in the {@code ImmutableMultimap} class. + * + * @since 11 + */ + @Beta + public ImmutableListMultimap inverse() { + ImmutableListMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableListMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableListMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableList removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableList replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder + = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + Object[] array = new Object[valueCount]; + for (int j = 0; j < valueCount; j++) { + array[j] = stream.readObject(); + } + builder.put(key, ImmutableList.copyOf(array)); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) + new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + @GwtIncompatible("Not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ImmutableMap.java b/guava/src/com/google/common/collect/ImmutableMap.java new file mode 100644 index 0000000..05e3711 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMap.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable, hash-based {@link Map} with reliable user-specified iteration + * order. Does not permit null keys or values. + * + *

Unlike {@link Collections#unmodifiableMap}, which is a view of a + * separate map which can still change, an instance of {@code ImmutableMap} + * contains its own data and will never change. {@code ImmutableMap} is + * convenient for {@code public static final} maps ("constant maps") and also + * lets you easily make a "defensive copy" of a map provided to your class by a + * caller. + * + *

Performance notes: unlike {@link HashMap}, {@code ImmutableMap} is + * not optimized for element types that have slow {@link Object#equals} or + * {@link Object#hashCode} implementations. You can get better performance by + * having your element type cache its own hash codes, and by making use of the + * cached values to short-circuit a slow {@code equals} algorithm. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMap implements Map, Serializable { + /** + * Returns the empty map. This map behaves and performs comparably to + * {@link Collections#emptyMap}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableMap of() { + return (ImmutableMap) EmptyImmutableMap.INSTANCE; + } + + /** + * Returns an immutable map containing a single entry. This map behaves and + * performs comparably to {@link Collections#singletonMap} but will not accept + * a null key or value. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static ImmutableMap of(K k1, V v1) { + return new SingletonImmutableMap( + checkNotNull(k1), checkNotNull(v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + return new RegularImmutableMap(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableMap( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableMap( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableMap(entryOf(k1, v1), + entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Verifies that {@code key} and {@code value} are non-null, and returns a new + * immutable entry with those values. + * + *

A call to {@link Map.Entry#setValue} on the returned entry will always + * throw {@link UnsupportedOperationException}. + */ + static Entry entryOf(K key, V value) { + checkNotNull(key, "null key in entry: null=%s", value); + checkNotNull(value, "null value in entry: %s=null", key); + return Maps.immutableEntry(key, value); + } + + /** + * A builder for creating immutable map instances, especially {@code public + * static final} maps ("constant maps"). Example:

   {@code
+   *
+   *   static final ImmutableMap WORD_TO_INT =
+   *       new ImmutableMap.Builder()
+   *           .put("one", 1)
+   *           .put("two", 2)
+   *           .put("three", 3)
+   *           .build();}
+ * + * For small immutable maps, the {@code ImmutableMap.of()} methods are + * even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple maps in series. Each map is a superset of + * the maps created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static class Builder { + final ArrayList> entries = Lists.newArrayList(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(K key, V value) { + entries.add(entryOf(key, value)); + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if + * necessary. Duplicate keys are not allowed, and will cause {@link #build} + * to fail. + * + * @since 11.0 + */ + public Builder put(Entry entry) { + K key = entry.getKey(); + V value = entry.getValue(); + if (entry instanceof ImmutableEntry) { + checkNotNull(key); + checkNotNull(value); + @SuppressWarnings("unchecked") // all supported methods are covariant + Entry immutableEntry = (Entry) entry; + entries.add(immutableEntry); + } else { + // Directly calling entryOf(entry.getKey(), entry.getValue()) can cause + // compilation error in Eclipse. + entries.add(entryOf(key, value)); + } + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. + * Duplicate keys are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public Builder putAll(Map map) { + entries.ensureCapacity(entries.size() + map.size()); + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + /* + * TODO(kevinb): Should build() and the ImmutableBiMap & ImmutableSortedMap + * versions throw an IllegalStateException instead? + */ + + /** + * Returns a newly-created immutable map. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap build() { + return fromEntryList(entries); + } + + private static ImmutableMap fromEntryList( + List> entries) { + int size = entries.size(); + switch (size) { + case 0: + return of(); + case 1: + return new SingletonImmutableMap(getOnlyElement(entries)); + default: + Entry[] entryArray + = entries.toArray(new Entry[entries.size()]); + return new RegularImmutableMap(entryArray); + } + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableMap copyOf( + Map map) { + if ((map instanceof ImmutableMap) && !(map instanceof ImmutableSortedMap)) { + // TODO(user): Make ImmutableMap.copyOf(immutableBiMap) call copyOf() + // on the ImmutableMap delegate(), rather than the bimap itself + + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + + @SuppressWarnings("unchecked") // we won't write to this array + Entry[] entries = map.entrySet().toArray(new Entry[0]); + switch (entries.length) { + case 0: + return of(); + case 1: + return new SingletonImmutableMap(entryOf( + entries[0].getKey(), entries[0].getValue())); + default: + for (int i = 0; i < entries.length; i++) { + K k = entries[i].getKey(); + V v = entries[i].getValue(); + entries[i] = entryOf(k, v); + } + return new RegularImmutableMap(entries); + } + } + + ImmutableMap() {} + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(@Nullable Object value) { + return value != null && Maps.containsValueImpl(this, value); + } + + // Overriding to mark it Nullable + @Override + public abstract V get(@Nullable Object key); + + private transient ImmutableSet> entrySet; + + /** + * Returns an immutable set of the mappings in this map. The entries are in + * the same order as the parameters used to build this map. + */ + @Override + public ImmutableSet> entrySet() { + ImmutableSet> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + abstract ImmutableSet> createEntrySet(); + + private transient ImmutableSet keySet; + + /** + * Returns an immutable set of the keys in this map. These keys are in + * the same order as the parameters used to build this map. + */ + @Override + public ImmutableSet keySet() { + ImmutableSet result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + ImmutableSet createKeySet() { + return new ImmutableMapKeySet(entrySet()) { + @Override ImmutableMap map() { + return ImmutableMap.this; + } + }; + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this map. The values are + * in the same order as the parameters used to build this map. + */ + @Override + public ImmutableCollection values() { + ImmutableCollection result = values; + return (result == null) ? values = createValues() : result; + } + + ImmutableCollection createValues() { + return new ImmutableMapValues() { + @Override ImmutableMap map() { + return ImmutableMap.this; + } + }; + } + + @Override public boolean equals(@Nullable Object object) { + return Maps.equalsImpl(this, object); + } + + abstract boolean isPartialView(); + + @Override public int hashCode() { + // not caching hash code since it could change if map values are mutable + // in a way that modifies their hash codes + return entrySet().hashCode(); + } + + @Override public String toString() { + return Maps.toStringImpl(this); + } + + /** + * Serialized type for all ImmutableMap instances. It captures the logical + * contents and they are reconstructed using public factory methods. This + * ensures that the implementation types remain as implementation details. + */ + static class SerializedForm implements Serializable { + private final Object[] keys; + private final Object[] values; + SerializedForm(ImmutableMap map) { + keys = new Object[map.size()]; + values = new Object[map.size()]; + int i = 0; + for (Entry entry : map.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + i++; + } + } + Object readResolve() { + Builder builder = new Builder(); + return createMap(builder); + } + Object createMap(Builder builder) { + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i], values[i]); + } + return builder.build(); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableMapEntrySet.java b/guava/src/com/google/common/collect/ImmutableMapEntrySet.java new file mode 100644 index 0000000..a6aa6e0 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMapEntrySet.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * {@code entrySet()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class ImmutableMapEntrySet extends ImmutableSet> { + ImmutableMapEntrySet() {} + + abstract ImmutableMap map(); + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean contains(@Nullable Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + V value = map().get(entry.getKey()); + return value != null && value.equals(entry.getValue()); + } + return false; + } + + @Override + boolean isPartialView() { + return map().isPartialView(); + } + + @GwtIncompatible("serialization") + @Override + Object writeReplace() { + return new EntrySetSerializedForm(map()); + } + + @GwtIncompatible("serialization") + private static class EntrySetSerializedForm implements Serializable { + final ImmutableMap map; + EntrySetSerializedForm(ImmutableMap map) { + this.map = map; + } + Object readResolve() { + return map.entrySet(); + } + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/ImmutableMapKeySet.java b/guava/src/com/google/common/collect/ImmutableMapKeySet.java new file mode 100644 index 0000000..56d1796 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMapKeySet.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * {@code keySet()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class ImmutableMapKeySet extends TransformedImmutableSet, K> { + ImmutableMapKeySet(ImmutableSet> entrySet) { + super(entrySet); + } + + ImmutableMapKeySet(ImmutableSet> entrySet, int hashCode) { + super(entrySet, hashCode); + } + + abstract ImmutableMap map(); + + @Override + K transform(Entry entry) { + return entry.getKey(); + } + + @Override + public boolean contains(@Nullable Object object) { + return map().containsKey(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + ImmutableList createAsList() { + final ImmutableList> entryList = map().entrySet().asList(); + return new ImmutableAsList() { + @Override + public K get(int index) { + return entryList.get(index).getKey(); + } + + @Override + ImmutableCollection delegateCollection() { + return ImmutableMapKeySet.this; + } + }; + } + + @GwtIncompatible("serialization") + @Override Object writeReplace() { + return new KeySetSerializedForm(map()); + } + + @GwtIncompatible("serialization") + private static class KeySetSerializedForm implements Serializable { + final ImmutableMap map; + KeySetSerializedForm(ImmutableMap map) { + this.map = map; + } + Object readResolve() { + return map.keySet(); + } + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/ImmutableMapValues.java b/guava/src/com/google/common/collect/ImmutableMapValues.java new file mode 100644 index 0000000..d16285b --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMapValues.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.Map.Entry; + +/** + * {@code values()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class ImmutableMapValues extends ImmutableCollection { + ImmutableMapValues() {} + + abstract ImmutableMap map(); + + @Override + public int size() { + return map().size(); + } + + @Override + public UnmodifiableIterator iterator() { + return Maps.valueIterator(map().entrySet().iterator()); + } + + @Override + public boolean contains(Object object) { + return map().containsValue(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + ImmutableList createAsList() { + final ImmutableList> entryList = map().entrySet().asList(); + return new ImmutableAsList() { + @Override + public V get(int index) { + return entryList.get(index).getValue(); + } + + @Override + ImmutableCollection delegateCollection() { + return ImmutableMapValues.this; + } + }; + } + + @GwtIncompatible("serialization") + @Override Object writeReplace() { + return new SerializedForm(map()); + } + + @GwtIncompatible("serialization") + private static class SerializedForm implements Serializable { + final ImmutableMap map; + SerializedForm(ImmutableMap map) { + this.map = map; + } + Object readResolve() { + return map.values(); + } + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/ImmutableMultimap.java b/guava/src/com/google/common/collect/ImmutableMultimap.java new file mode 100644 index 0000000..8b6aa29 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMultimap.java @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An immutable {@link Multimap}. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableMultimap(Multimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableMultimap} contains its own data and will never + * change. {@code ImmutableMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + *

In addition to methods defined by {@link Multimap}, an {@link #inverse} + * method is also supported. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public abstract class ImmutableMultimap + implements Multimap, Serializable { + + /** Returns an empty multimap. */ + public static ImmutableMultimap of() { + return ImmutableListMultimap.of(); + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableMultimap of(K k1, V v1) { + return ImmutableListMultimap.of(k1, v1); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2) { + return ImmutableListMultimap.of(k1, v1, k2, v2); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Multimap for {@link ImmutableMultimap.Builder} that maintains key and + * value orderings, allows duplicate values, and performs better than + * {@link LinkedListMultimap}. + */ + private static class BuilderMultimap extends AbstractMultimap { + BuilderMultimap() { + super(new LinkedHashMap>()); + } + @Override Collection createCollection() { + return Lists.newArrayList(); + } + private static final long serialVersionUID = 0; + } + + /** + * A builder for creating immutable multimap instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple multimaps in series. Each multimap contains the + * key-value mappings in the previously created multimaps. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static class Builder { + Multimap builderMultimap = new BuilderMultimap(); + Comparator keyComparator; + Comparator valueComparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMultimap#builder}. + */ + public Builder() {} + + /** + * Adds a key-value mapping to the built multimap. + */ + public Builder put(K key, V value) { + builderMultimap.put(checkNotNull(key), checkNotNull(value)); + return this; + } + + /** + * Adds an entry to the built multimap. + * + * @since 11.0 + */ + public Builder put(Entry entry) { + builderMultimap.put( + checkNotNull(entry.getKey()), checkNotNull(entry.getValue())); + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any + * element in {@code values} is null. The builder is left in an invalid + * state. + */ + public Builder putAll(K key, Iterable values) { + Collection valueList = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + valueList.add(checkNotNull(value)); + } + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The builder + * is left in an invalid state. + */ + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + /** + * Stores another multimap's entries in the built multimap. The generated + * multimap's key and value orderings correspond to the iteration ordering + * of the {@code multimap.asMap()} view, with new keys and values following + * any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null. The builder is left in an invalid state. + */ + public Builder putAll(Multimap multimap) { + for (Entry> entry + : multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Specifies the ordering of the generated multimap's keys. + * + * @since 8.0 + */ + @Beta + public Builder orderKeysBy(Comparator keyComparator) { + this.keyComparator = checkNotNull(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + * + * @since 8.0 + */ + @Beta + public Builder orderValuesBy(Comparator valueComparator) { + this.valueComparator = checkNotNull(valueComparator); + return this; + } + + /** + * Returns a newly-created immutable multimap. + */ + public ImmutableMultimap build() { + if (valueComparator != null) { + for (Collection values : builderMultimap.asMap().values()) { + List list = (List ) values; + Collections.sort(list, valueComparator); + } + } + if (keyComparator != null) { + Multimap sortedCopy = new BuilderMultimap(); + List>> entries = Lists.newArrayList( + builderMultimap.asMap().entrySet()); + Collections.sort( + entries, + Ordering.from(keyComparator).onResultOf(new Function>, K>() { + @Override + public K apply(Entry> entry) { + return entry.getKey(); + } + })); + for (Map.Entry> entry : entries) { + sortedCopy.putAll(entry.getKey(), entry.getValue()); + } + builderMultimap = sortedCopy; + } + return copyOf(builderMultimap); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code + * multimap}. The generated multimap's key and value orderings correspond to + * the iteration ordering of the {@code multimap.asMap()} view. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableMultimap copyOf( + Multimap multimap) { + if (multimap instanceof ImmutableMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableMultimap kvMultimap + = (ImmutableMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + return ImmutableListMultimap.copyOf(multimap); + } + + final transient ImmutableMap> map; + final transient int size; + + // These constants allow the deserialization code to set final fields. This + // holder class makes sure they are not initialized unless an instance is + // deserialized. + @GwtIncompatible("java serialization is not supported") + static class FieldSettersHolder { + static final Serialization.FieldSetter + MAP_FIELD_SETTER = Serialization.getFieldSetter( + ImmutableMultimap.class, "map"); + static final Serialization.FieldSetter + SIZE_FIELD_SETTER = Serialization.getFieldSetter( + ImmutableMultimap.class, "size"); + } + + ImmutableMultimap(ImmutableMap> map, + int size) { + this.map = map; + this.size = size; + } + + // mutators (not supported) + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public ImmutableCollection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public ImmutableCollection replaceValues(K key, + Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an immutable collection of the values for the given key. If no + * mappings in the multimap have the provided key, an empty immutable + * collection is returned. The values are in the same order as the parameters + * used to build this multimap. + */ + @Override + public abstract ImmutableCollection get(K key); + + /** + * Returns an immutable multimap which is the inverse of this one. For every + * key-value mapping in the original, the result will have a mapping with + * key and value reversed. + * + * @since 11.0 + */ + @Beta + public abstract ImmutableMultimap inverse(); + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + boolean isPartialView() { + return map.isPartialView(); + } + + // accessors + + @Override + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + Collection values = map.get(key); + return values != null && values.contains(value); + } + + @Override + public boolean containsKey(@Nullable Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(@Nullable Object value) { + for (Collection valueCollection : map.values()) { + if (valueCollection.contains(value)) { + return true; + } + } + return false; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public int size() { + return size; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.map.equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + @Override public String toString() { + return map.toString(); + } + + // views + + /** + * Returns an immutable set of the distinct keys in this multimap. These keys + * are ordered according to when they first appeared during the construction + * of this multimap. + */ + @Override + public ImmutableSet keySet() { + return map.keySet(); + } + + /** + * Returns an immutable map that associates each key with its corresponding + * values in the multimap. + */ + @Override + @SuppressWarnings("unchecked") // a widening cast + public ImmutableMap> asMap() { + return (ImmutableMap) map; + } + + private transient ImmutableCollection> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + @Override + public ImmutableCollection> entries() { + ImmutableCollection> result = entries; + return (result == null) + ? (entries = new EntryCollection(this)) : result; + } + + private static class EntryCollection + extends ImmutableCollection> { + final ImmutableMultimap multimap; + + EntryCollection(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override public UnmodifiableIterator> iterator() { + final Iterator>> + mapIterator = this.multimap.map.entrySet().iterator(); + + return new UnmodifiableIterator>() { + K key; + Iterator valueIterator; + + @Override + public boolean hasNext() { + return (key != null && valueIterator.hasNext()) + || mapIterator.hasNext(); + } + + @Override + public Entry next() { + if (key == null || !valueIterator.hasNext()) { + Entry> entry + = mapIterator.next(); + key = entry.getKey(); + valueIterator = entry.getValue().iterator(); + } + return Maps.immutableEntry(key, valueIterator.next()); + } + }; + } + + @Override boolean isPartialView() { + return multimap.isPartialView(); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + private transient ImmutableMultiset keys; + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times a key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Duplicate keys appear consecutively + * in the multiset's iteration order. + */ + @Override + public ImmutableMultiset keys() { + ImmutableMultiset result = keys; + return (result == null) ? (keys = createKeys()) : result; + } + + private ImmutableMultiset createKeys() { + return new Keys(); + } + + @SuppressWarnings("serial") // Uses writeReplace, not default serialization + class Keys extends ImmutableMultiset { + @Override + public boolean contains(@Nullable Object object) { + return containsKey(object); + } + + @Override + public int count(@Nullable Object element) { + Collection values = map.get(element); + return (values == null) ? 0 : values.size(); + } + + @Override + public Set elementSet() { + return keySet(); + } + + @Override + public int size() { + return ImmutableMultimap.this.size(); + } + + @Override + ImmutableSet> createEntrySet() { + return new KeysEntrySet(); + } + + private class KeysEntrySet extends ImmutableMultiset.EntrySet { + @Override + public int size() { + return keySet().size(); + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + final ImmutableList>> mapEntries = + map.entrySet().asList(); + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + Map.Entry> entry = mapEntries.get(index); + return Multisets.immutableEntry(entry.getKey(), entry.getValue().size()); + } + + @Override + ImmutableCollection> delegateCollection() { + return KeysEntrySet.this; + } + }; + } + } + + @Override + boolean isPartialView() { + return true; + } + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + @Override + public ImmutableCollection values() { + ImmutableCollection result = values; + return (result == null) ? (values = new Values(this)) : result; + } + + private static class Values extends ImmutableCollection { + final ImmutableMultimap multimap; + + Values(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override public UnmodifiableIterator iterator() { + return Maps.valueIterator(multimap.entries().iterator()); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override boolean isPartialView() { + return true; + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ImmutableMultiset.java b/guava/src/com/google/common/collect/ImmutableMultiset.java new file mode 100644 index 0000000..b909315 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableMultiset.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * An immutable hash-based multiset. Does not permit null elements. + * + *

Its iterator orders elements according to the first appearance of the + * element among the items passed to the factory method or builder. When the + * multiset contains multiple instances of an element, those instances are + * consecutive in the iteration order. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +// TODO(user): write an efficient asList() implementation +public abstract class ImmutableMultiset extends ImmutableCollection + implements Multiset { + + /** + * Returns the empty immutable multiset. + */ + @SuppressWarnings("unchecked") // all supported methods are covariant + public static ImmutableMultiset of() { + return (ImmutableMultiset) EmptyImmutableMultiset.INSTANCE; + } + + /** + * Returns an immutable multiset containing a single element. + * + * @throws NullPointerException if {@code element} is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // generic array created but never written + public static ImmutableMultiset of(E element) { + return copyOfInternal(element); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2) { + return copyOfInternal(e1, e2); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3) { + return copyOfInternal(e1, e2, e3); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3, E e4) { + return copyOfInternal(e1, e2, e3, e4); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5) { + return copyOfInternal(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of( + E e1, E e2, E e3, E e4, E e5, E e6, E... others) { + int size = others.length + 6; + List all = new ArrayList(size); + Collections.addAll(all, e1, e2, e3, e4, e5, e6); + Collections.addAll(all, others); + return copyOf(all); + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.copyOf([2, 3, 1, 3])} yields a multiset + * with elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 6.0 + */ + public static ImmutableMultiset copyOf(E[] elements) { + return copyOf(Arrays.asList(elements)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3))} yields + * a multiset with elements in the order {@code 2, 3, 3, 1}. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableMultiset}, no copy will actually be performed, and + * the given multiset itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf( + Iterable elements) { + if (elements instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableMultiset result = (ImmutableMultiset) elements; + if (!result.isPartialView()) { + return result; + } + } + + Multiset multiset = (elements instanceof Multiset) + ? Multisets.cast(elements) + : LinkedHashMultiset.create(elements); + + return copyOfInternal(multiset); + } + + private static ImmutableMultiset copyOfInternal(E... elements) { + return copyOf(Arrays.asList(elements)); + } + + private static ImmutableMultiset copyOfInternal( + Multiset multiset) { + return copyFromEntries(multiset.entrySet()); + } + + static ImmutableMultiset copyFromEntries( + Collection> entries) { + long size = 0; + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Entry entry : entries) { + int count = entry.getCount(); + if (count > 0) { + // Since ImmutableMap.Builder throws an NPE if an element is null, no + // other null checks are needed. + builder.put(entry.getElement(), count); + size += count; + } + } + + if (size == 0) { + return of(); + } + return new RegularImmutableMultiset( + builder.build(), Ints.saturatedCast(size)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, + * {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3).iterator())} + * yields a multiset with elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf( + Iterator elements) { + Multiset multiset = LinkedHashMultiset.create(); + Iterators.addAll(multiset, elements); + return copyOfInternal(multiset); + } + + ImmutableMultiset() {} + + @Override public UnmodifiableIterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + int remaining; + E element; + + @Override + public boolean hasNext() { + return (remaining > 0) || entryIterator.hasNext(); + } + + @Override + public E next() { + if (remaining <= 0) { + Entry entry = entryIterator.next(); + element = entry.getElement(); + remaining = entry.getCount(); + } + remaining--; + return element; + } + }; + } + + @Override + public boolean contains(@Nullable Object object) { + return count(object) > 0; + } + + @Override + public boolean containsAll(Collection targets) { + return elementSet().containsAll(targets); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + if (this.size() != that.size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + @Override public String toString() { + return entrySet().toString(); + } + + private transient ImmutableSet> entrySet; + + @Override + public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = createEntrySet()) : es; + } + + abstract ImmutableSet> createEntrySet(); + + abstract class EntrySet extends ImmutableSet> { + @Override + boolean isPartialView() { + return ImmutableMultiset.this.isPartialView(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + /* + * TODO(hhchan): Revert once we have a separate, manual emulation of this + * class. + */ + @Override + public Object[] toArray() { + Object[] newArray = new Object[size()]; + return toArray(newArray); + } + + /* + * TODO(hhchan): Revert once we have a separate, manual emulation of this + * class. + */ + @Override + public T[] toArray(T[] other) { + int size = size(); + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + + // Writes will produce ArrayStoreException when the toArray() doc requires + Object[] otherAsObjectArray = other; + int index = 0; + for (Entry element : this) { + otherAsObjectArray[index++] = element; + } + return other; + } + + @Override + public int hashCode() { + return ImmutableMultiset.this.hashCode(); + } + + // We can't label this with @Override, because it doesn't override anything + // in the GWT emulated version. + // TODO(cpovirk): try making all copies of this method @GwtIncompatible instead + Object writeReplace() { + return new EntrySetSerializedForm(ImmutableMultiset.this); + } + + private static final long serialVersionUID = 0; + } + + static class EntrySetSerializedForm implements Serializable { + final ImmutableMultiset multiset; + + EntrySetSerializedForm(ImmutableMultiset multiset) { + this.multiset = multiset; + } + + Object readResolve() { + return multiset.entrySet(); + } + } + + private static class SerializedForm implements Serializable { + final Object[] elements; + final int[] counts; + + SerializedForm(Multiset multiset) { + int distinct = multiset.entrySet().size(); + elements = new Object[distinct]; + counts = new int[distinct]; + int i = 0; + for (Entry entry : multiset.entrySet()) { + elements[i] = entry.getElement(); + counts[i] = entry.getCount(); + i++; + } + } + + Object readResolve() { + LinkedHashMultiset multiset = + LinkedHashMultiset.create(elements.length); + for (int i = 0; i < elements.length; i++) { + multiset.add(elements[i], counts[i]); + } + return ImmutableMultiset.copyOf(multiset); + } + + private static final long serialVersionUID = 0; + } + + // We can't label this with @Override, because it doesn't override anything + // in the GWT emulated version. + Object writeReplace() { + return new SerializedForm(this); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable multiset instances, especially {@code + * public static final} multisets ("constant multisets"). Example: + *
 {@code
+   *
+   *   public static final ImmutableMultiset BEANS =
+   *       new ImmutableMultiset.Builder()
+   *           .addCopies(Bean.COCOA, 4)
+   *           .addCopies(Bean.GARDEN, 6)
+   *           .addCopies(Bean.RED, 8)
+   *           .addCopies(Bean.BLACK_EYED, 10)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple multisets in series. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static class Builder extends ImmutableCollection.Builder { + final Multiset contents; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMultiset#builder}. + */ + public Builder() { + this(LinkedHashMultiset.create()); + } + + Builder(Multiset contents) { + this.contents = contents; + } + + /** + * Adds {@code element} to the {@code ImmutableMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + contents.add(checkNotNull(element)); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code + * ImmutableMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May + * be zero, in which case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or + * if this operation would result in more than {@link Integer#MAX_VALUE} + * occurrences of the element + */ + public Builder addCopies(E element, int occurrences) { + contents.add(checkNotNull(element), occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the + * element attains the desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + public Builder setCount(E element, int count) { + contents.setCount(checkNotNull(element), count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code + * ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Multiset) { + Multiset multiset = Multisets.cast(elements); + for (Entry entry : multiset.entrySet()) { + addCopies(entry.getElement(), entry.getCount()); + } + } else { + super.addAll(elements); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableMultiset} based on the contents + * of the {@code Builder}. + */ + @Override public ImmutableMultiset build() { + return copyOf(contents); + } + } +} diff --git a/guava/src/com/google/common/collect/ImmutableSet.java b/guava/src/com/google/common/collect/ImmutableSet.java new file mode 100644 index 0000000..55417a3 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSet.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A high-performance, immutable {@code Set} with reliable, user-specified + * iteration order. Does not permit null elements. + * + *

Unlike {@link Collections#unmodifiableSet}, which is a view of a + * separate collection that can still change, an instance of this class contains + * its own private data and will never change. This class is convenient + * for {@code public static final} sets ("constant sets") and also lets you + * easily make a "defensive copy" of a set provided to your class by a caller. + * + *

Warning: Like most sets, an {@code ImmutableSet} will not function + * correctly if an element is modified after being placed in the set. For this + * reason, and to avoid general confusion, it is strongly recommended to place + * only immutable objects into this collection. + * + *

This class has been observed to perform significantly better than {@link + * HashSet} for objects with very fast {@link Object#hashCode} implementations + * (as a well-behaved immutable object should). While this class's factory + * methods create hash-based instances, the {@link ImmutableSortedSet} subclass + * performs binary searches instead. + * + *

Note: Although this class is not final, it cannot be subclassed + * outside its package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @see ImmutableList + * @see ImmutableMap + * @author Kevin Bourrillion + * @author Nick Kralevich + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSet extends ImmutableCollection + implements Set { + /** + * Returns the empty immutable set. This set behaves and performs comparably + * to {@link Collections#emptySet}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings({"unchecked"}) + public static ImmutableSet of() { + return (ImmutableSet) EmptyImmutableSet.INSTANCE; + } + + /** + * Returns an immutable set containing a single element. This set behaves and + * performs comparably to {@link Collections#singleton}, but will not accept + * a null element. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static ImmutableSet of(E element) { + return new SingletonImmutableSet(element); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableSet of(E e1, E e2) { + return construct(2, e1, e2); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableSet of(E e1, E e2, E e3) { + return construct(3, e1, e2, e3); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4) { + return construct(4, e1, e2, e3, e4); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5) { + return construct(5, e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + * @since 3.0 (source-compatible since 2.0) + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5, E e6, + E... others) { + final int paramCount = 6; + Object[] elements = new Object[paramCount + others.length]; + elements[0] = e1; + elements[1] = e2; + elements[2] = e3; + elements[3] = e4; + elements[4] = e5; + elements[5] = e6; + System.arraycopy(others, 0, elements, paramCount, others.length); + return construct(elements.length, elements); + } + + /** + * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array. + * If {@code k} is the size of the returned {@code ImmutableSet}, then the unique elements of + * {@code elements} will be in the first {@code k} positions, and {@code elements[i] == null} for + * {@code k <= i < n}. + * + *

This may modify {@code elements}. Additionally, if {@code n == elements.length} and + * {@code elements} contains no duplicates, {@code elements} may be used without copying in the + * returned {@code ImmutableSet}, in which case it may no longer be modified. + * + *

{@code elements} may contain only values of type {@code E}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is + * null + */ + private static ImmutableSet construct(int n, Object... elements) { + switch (n) { + case 0: + return of(); + case 1: { + @SuppressWarnings("unchecked") // safe; elements contains only E's + E elem = (E) elements[0]; + return of(elem); + } + } + int tableSize = chooseTableSize(n); + Object[] table = new Object[tableSize]; + int mask = tableSize - 1; + int hashCode = 0; + int uniques = 0; + for (int i = 0; i < n; i++) { + Object element = ObjectArrays.checkElementNotNull(elements[i], i); + int hash = element.hashCode(); + for (int j = Hashing.smear(hash); ; j++) { + int index = j & mask; + Object value = table[index]; + if (value == null) { + // Came to an empty slot. Put the element here. + elements[uniques++] = element; + table[index] = element; + hashCode += hash; + break; + } else if (value.equals(element)) { + break; + } + } + } + Arrays.fill(elements, uniques, n, null); + if (uniques == 1) { + // There is only one element or elements are all duplicates + @SuppressWarnings("unchecked") // we are careful to only pass in E + E element = (E) elements[0]; + return new SingletonImmutableSet(element, hashCode); + } else if (tableSize != chooseTableSize(uniques)) { + // Resize the table when the array includes too many duplicates. + // when this happens, we have already made a copy + return construct(uniques, elements); + } else { + Object[] uniqueElements = (uniques < elements.length) + ? ObjectArrays.arraysCopyOf(elements, uniques) + : elements; + return new RegularImmutableSet(uniqueElements, hashCode, table, mask); + } + } + + // We use power-of-2 tables, and this is the highest int that's a power of 2 + static final int MAX_TABLE_SIZE = Ints.MAX_POWER_OF_TWO; + + // Represents how tightly we can pack things, as a maximum. + private static final double DESIRED_LOAD_FACTOR = 0.7; + + // If the set has this many elements, it will "max out" the table size + private static final int CUTOFF = + (int) Math.floor(MAX_TABLE_SIZE * DESIRED_LOAD_FACTOR); + + /** + * Returns an array size suitable for the backing array of a hash table that + * uses open addressing with linear probing in its implementation. The + * returned size is the smallest power of two that can hold setSize elements + * with the desired load factor. + * + *

Do not call this method with setSize < 2. + */ + @VisibleForTesting static int chooseTableSize(int setSize) { + // Correct the size for open addressing to match desired load factor. + if (setSize < CUTOFF) { + // Round up to the next highest power of 2. + int tableSize = Integer.highestOneBit(setSize - 1) << 1; + while (tableSize * DESIRED_LOAD_FACTOR < setSize) { + tableSize <<= 1; + } + return tableSize; + } + + // The table can't be completely full or we'll get infinite reprobes + checkArgument(setSize < MAX_TABLE_SIZE, "collection too large"); + return MAX_TABLE_SIZE; + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 3.0 + */ + public static ImmutableSet copyOf(E[] elements) { + // TODO(benyu): could we delegate to + // copyFromCollection(Arrays.asList(elements))? + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return construct(elements.length, elements.clone()); + } + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Set}, then {@code + * ImmutableSet.copyOf(s)} returns an {@code ImmutableSet} containing + * each of the strings in {@code s}, while {@code ImmutableSet.of(s)} returns + * a {@code ImmutableSet>} containing one element (the given set + * itself). + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterable elements) { + return (elements instanceof Collection) + ? copyOf(Collections2.cast(elements)) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but anything further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new ImmutableSet.Builder() + .add(first) + .addAll(elements) + .build(); + } + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. This method iterates over {@code elements} at most + * once. + * + *

Note that if {@code s} is a {@code Set}, then {@code + * ImmutableSet.copyOf(s)} returns an {@code ImmutableSet} containing + * each of the strings in {@code s}, while {@code ImmutableSet.of(s)} returns + * a {@code ImmutableSet>} containing one element (the given set + * itself). + * + *

Note: Despite what the method name suggests, {@code copyOf} will + * return constant-space views, rather than linear-space copies, of some + * inputs known to be immutable. For some other immutable inputs, such as key + * sets of an {@code ImmutableMap}, it still performs a copy in order to avoid + * holding references to the values of the map. The heuristics used in this + * decision are undocumented and subject to change except that: + *

    + *
  • A full copy will be done of any {@code ImmutableSortedSet}.
  • + *
  • {@code ImmutableSet.copyOf()} is idempotent with respect to pointer + * equality.
  • + *
+ * + *

This method is safe to use even when {@code elements} is a synchronized + * or concurrent collection that is currently being modified by another + * thread. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSet copyOf(Collection elements) { + if (elements instanceof ImmutableSet + && !(elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableSet set = (ImmutableSet) elements; + if (!set.isPartialView()) { + return set; + } + } + return copyFromCollection(elements); + } + + private static ImmutableSet copyFromCollection( + Collection collection) { + Object[] elements = collection.toArray(); + switch (elements.length) { + case 0: + return of(); + case 1: + @SuppressWarnings("unchecked") // collection had only Es in it + E onlyElement = (E) elements[0]; + return of(onlyElement); + default: + // safe to use the array without copying it + // as specified by Collection.toArray(). + return construct(elements.length, elements); + } + } + + ImmutableSet() {} + + /** Returns {@code true} if the {@code hashCode()} method runs quickly. */ + boolean isHashCodeFast() { + return false; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableSet + && isHashCodeFast() + && ((ImmutableSet) object).isHashCodeFast() + && hashCode() != object.hashCode()) { + return false; + } + return Sets.equalsImpl(this, object); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + // This declaration is needed to make Set.iterator() and + // ImmutableCollection.iterator() consistent. + @Override public abstract UnmodifiableIterator iterator(); + + abstract static class ArrayImmutableSet extends ImmutableSet { + // the elements (two or more) in the desired order. + final transient Object[] elements; + + ArrayImmutableSet(Object[] elements) { + this.elements = elements; + } + + @Override + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return asList().iterator(); + } + + @Override public Object[] toArray() { + return asList().toArray(); + } + + @Override public T[] toArray(T[] array) { + return asList().toArray(array); + } + + @Override public boolean containsAll(Collection targets) { + if (targets == this) { + return true; + } + if (!(targets instanceof ArrayImmutableSet)) { + return super.containsAll(targets); + } + if (targets.size() > size()) { + return false; + } + for (Object target : ((ArrayImmutableSet) targets).elements) { + if (!contains(target)) { + return false; + } + } + return true; + } + + @Override boolean isPartialView() { + return false; + } + + @Override ImmutableList createAsList() { + return new RegularImmutableAsList(this, elements); + } + } + + /* + * This class is used to serialize all ImmutableSet instances, except for + * ImmutableEnumSet/ImmutableSortedSet, regardless of implementation type. It + * captures their "logical contents" and they are reconstructed using public + * static factories. This is necessary to ensure that the existence of a + * particular implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return copyOf(elements); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable set instances, especially {@code public + * static final} sets ("constant sets"). Example:

   {@code
+   *
+   *   public static final ImmutableSet GOOGLE_COLORS =
+   *       new ImmutableSet.Builder()
+   *           .addAll(WEBSAFE_COLORS)
+   *           .add(new Color(0, 191, 255))
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple sets in series. Each set is a superset of the set + * created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static class Builder extends ImmutableCollection.Builder { + Object[] contents; + int size; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSet#builder}. + */ + public Builder() { + this(DEFAULT_INITIAL_CAPACITY); + } + + Builder(int capacity) { + checkArgument(capacity >= 0, "capacity must be >= 0 but was %s", capacity); + this.contents = new Object[capacity]; + this.size = 0; + } + + /** + * Expand capacity to allow the specified number of elements to be added. + */ + Builder expandFor(int count) { + int minCapacity = size + count; + if (contents.length < minCapacity) { + contents = ObjectArrays.arraysCopyOf( + contents, expandedCapacity(contents.length, minCapacity)); + } + return this; + } + + /** + * Adds {@code element} to the {@code ImmutableSet}. If the {@code + * ImmutableSet} already contains {@code element}, then {@code add} has no + * effect (only the previously added element is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + expandFor(1); + contents[size++] = checkNotNull(element); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + for (int i = 0; i < elements.length; i++) { + ObjectArrays.checkElementNotNull(elements[i], i); + } + expandFor(elements.length); + System.arraycopy(elements, 0, contents, size, elements.length); + size += elements.length; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the {@code Iterable} to add to the {@code ImmutableSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Collection) { + Collection collection = (Collection) elements; + expandFor(collection.size()); + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSet} based on the contents of + * the {@code Builder}. + */ + @Override public ImmutableSet build() { + ImmutableSet result = construct(size, contents); + // construct has the side effect of deduping contents, so we update size + // accordingly. + size = result.size(); + return result; + } + } +} diff --git a/guava/src/com/google/common/collect/ImmutableSetMultimap.java b/guava/src/com/google/common/collect/ImmutableSetMultimap.java new file mode 100644 index 0000000..e184db4 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSetMultimap.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * An immutable {@link SetMultimap} with reliable user-specified key and value + * iteration order. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableSetMultimap(SetMultimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableSetMultimap} contains its own data and will never + * change. {@code ImmutableSetMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Mike Ward + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public class ImmutableSetMultimap + extends ImmutableMultimap + implements SetMultimap { + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableSetMultimap of() { + return (ImmutableSetMultimap) EmptyImmutableSetMultimap.INSTANCE; + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableSetMultimap of(K k1, V v1) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Multimap for {@link ImmutableSetMultimap.Builder} that maintains key + * and value orderings and performs better than {@link LinkedHashMultimap}. + */ + private static class BuilderMultimap extends AbstractMultimap { + BuilderMultimap() { + super(new LinkedHashMap>()); + } + @Override Collection createCollection() { + return Sets.newLinkedHashSet(); + } + private static final long serialVersionUID = 0; + } + + /** + * Multimap for {@link ImmutableSetMultimap.Builder} that sorts keys and + * maintains value orderings. + */ + private static class SortedKeyBuilderMultimap + extends AbstractMultimap { + SortedKeyBuilderMultimap( + Comparator keyComparator, Multimap multimap) { + super(new TreeMap>(keyComparator)); + putAll(multimap); + } + @Override Collection createCollection() { + return Sets.newLinkedHashSet(); + } + private static final long serialVersionUID = 0; + } + + /** + * A builder for creating immutable {@code SetMultimap} instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableSetMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple multimaps in series. Each multimap contains the + * key-value mappings in the previously created multimaps. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder + extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSetMultimap#builder}. + */ + public Builder() { + builderMultimap = new BuilderMultimap(); + } + + /** + * Adds a key-value mapping to the built multimap if it is not already + * present. + */ + @Override public Builder put(K key, V value) { + builderMultimap.put(checkNotNull(key), checkNotNull(value)); + return this; + } + + /** + * Adds an entry to the built multimap if it is not already present. + * + * @since 11.0 + */ + @Override public Builder put(Entry entry) { + builderMultimap.put( + checkNotNull(entry.getKey()), checkNotNull(entry.getValue())); + return this; + } + + @Override public Builder putAll(K key, Iterable values) { + Collection collection = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + collection.add(checkNotNull(value)); + } + return this; + } + + @Override public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + @Override public Builder putAll( + Multimap multimap) { + for (Entry> entry + : multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + @Beta @Override + public Builder orderKeysBy(Comparator keyComparator) { + this.keyComparator = checkNotNull(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + * + *

If this method is called, the sets returned by the {@code get()} + * method of the generated multimap and its {@link Multimap#asMap()} view + * are {@link ImmutableSortedSet} instances. However, serialization does not + * preserve that property, though it does maintain the key and value + * ordering. + * + * @since 8.0 + */ + // TODO: Make serialization behavior consistent. + @Beta @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** + * Returns a newly-created immutable set multimap. + */ + @Override public ImmutableSetMultimap build() { + if (keyComparator != null) { + Multimap sortedCopy = new BuilderMultimap(); + List>> entries = Lists.newArrayList( + builderMultimap.asMap().entrySet()); + Collections.sort( + entries, + Ordering.from(keyComparator).onResultOf(new Function>, K>() { + @Override + public K apply(Entry> entry) { + return entry.getKey(); + } + })); + for (Map.Entry> entry : entries) { + sortedCopy.putAll(entry.getKey(), entry.getValue()); + } + builderMultimap = sortedCopy; + } + return copyOf(builderMultimap, valueComparator); + } + } + + /** + * Returns an immutable set multimap containing the same mappings as + * {@code multimap}. The generated multimap's key and value orderings + * correspond to the iteration ordering of the {@code multimap.asMap()} view. + * Repeated occurrences of an entry in the multimap after the first are + * ignored. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableSetMultimap copyOf( + Multimap multimap) { + return copyOf(multimap, null); + } + + private static ImmutableSetMultimap copyOf( + Multimap multimap, + Comparator valueComparator) { + checkNotNull(multimap); // eager for GWT + if (multimap.isEmpty() && valueComparator == null) { + return of(); + } + + if (multimap instanceof ImmutableSetMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableSetMultimap kvMultimap + = (ImmutableSetMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int size = 0; + + for (Entry> entry + : multimap.asMap().entrySet()) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableSet set = (valueComparator == null) + ? ImmutableSet.copyOf(values) + : ImmutableSortedSet.copyOf(valueComparator, values); + if (!set.isEmpty()) { + builder.put(key, set); + size += set.size(); + } + } + + return new ImmutableSetMultimap( + builder.build(), size, valueComparator); + } + + // Returned by get() when values are sorted and a missing key is provided. + private final transient ImmutableSortedSet emptySet; + + ImmutableSetMultimap(ImmutableMap> map, int size, + @Nullable Comparator valueComparator) { + super(map, size); + this.emptySet = (valueComparator == null) + ? null : ImmutableSortedSet.emptySet(valueComparator); + } + + // views + + /** + * Returns an immutable set of the values for the given key. If no mappings + * in the multimap have the provided key, an empty immutable set is returned. + * The values are in the same order as the parameters used to build this + * multimap. + */ + @Override public ImmutableSet get(@Nullable K key) { + // This cast is safe as its type is known in constructor. + ImmutableSet set = (ImmutableSet) map.get(key); + if (set != null) { + return set; + } else if (emptySet != null) { + return emptySet; + } else { + return ImmutableSet.of(); + } + } + + private transient ImmutableSetMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a set multimap cannot contain multiple pairs with + * the same key and value, this method returns an {@code ImmutableSetMultimap} + * rather than the {@code ImmutableMultimap} specified in the {@code + * ImmutableMultimap} class. + * + * @since 11.0 + */ + @Beta + public ImmutableSetMultimap inverse() { + ImmutableSetMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableSetMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableSetMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableSet replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private transient ImmutableSet> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. + * Its iterator traverses the values for the first key, the values for the + * second key, and so on. + */ + // TODO(kevinb): Fix this so that two copies of the entries are not created. + @Override public ImmutableSet> entries() { + ImmutableSet> result = entries; + return (result == null) + ? (entries = ImmutableSet.copyOf(super.entries())) + : result; + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder + = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + Object[] array = new Object[valueCount]; + for (int j = 0; j < valueCount; j++) { + array[j] = stream.readObject(); + } + ImmutableSet valueSet = ImmutableSet.copyOf(array); + if (valueSet.size() != array.length) { + throw new InvalidObjectException( + "Duplicate key-value pairs exist for key " + key); + } + builder.put(key, valueSet); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) + new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + @GwtIncompatible("not needed in emulated source.") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ImmutableSortedAsList.java b/guava/src/com/google/common/collect/ImmutableSortedAsList.java new file mode 100644 index 0000000..98aab41 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedAsList.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.util.Comparator; + +import javax.annotation.Nullable; + +/** + * List returned by {@code ImmutableSortedSet.asList()} when the set isn't empty. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") +final class ImmutableSortedAsList extends RegularImmutableAsList + implements SortedIterable { + ImmutableSortedAsList( + ImmutableSortedSet backingSet, ImmutableList backingList) { + super(backingSet, backingList); + } + + @Override + ImmutableSortedSet delegateCollection() { + return (ImmutableSortedSet) super.delegateCollection(); + } + + @Override public Comparator comparator() { + return delegateCollection().comparator(); + } + + // Override indexOf() and lastIndexOf() to be O(log N) instead of O(N). + + @GwtIncompatible("ImmutableSortedSet.indexOf") + // TODO(cpovirk): consider manual binary search under GWT to preserve O(log N) lookup + @Override public int indexOf(@Nullable Object target) { + int index = delegateCollection().indexOf(target); + + // TODO(kevinb): reconsider if it's really worth making feeble attempts at + // sanity for inconsistent comparators. + + // The equals() check is needed when the comparator isn't compatible with + // equals(). + return (index >= 0 && get(index).equals(target)) ? index : -1; + } + + @GwtIncompatible("ImmutableSortedSet.indexOf") + @Override public int lastIndexOf(@Nullable Object target) { + return indexOf(target); + } + + @Override + public boolean contains(Object target) { + // Necessary for ISS's with comparators inconsistent with equals. + return indexOf(target) >= 0; + } + + @GwtIncompatible("super.subListUnchecked does not exist; inherited subList is valid if slow") + /* + * TODO(cpovirk): if we start to override indexOf/lastIndexOf under GWT, we'll want some way to + * override subList to return an ImmutableSortedAsList for better performance. Right now, I'm not + * sure there's any performance hit from our failure to override subListUnchecked under GWT + */ + @Override + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + return new RegularImmutableSortedSet( + super.subListUnchecked(fromIndex, toIndex), comparator()) + .asList(); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableSortedMap.java b/guava/src/com/google/common/collect/ImmutableSortedMap.java new file mode 100644 index 0000000..bd2340d --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedMap.java @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.keyOrNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * An immutable {@link SortedMap}. Does not permit null keys or values. + * + *

Unlike {@link Collections#unmodifiableSortedMap}, which is a view + * of a separate map which can still change, an instance of {@code + * ImmutableSortedMap} contains its own data and will never change. + * {@code ImmutableSortedMap} is convenient for {@code public static final} maps + * ("constant maps") and also lets you easily make a "defensive copy" of a map + * provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library; implements {@code + * NavigableMap} since 12.0) + */ +@GwtCompatible(serializable = true, emulated = true) +public abstract class ImmutableSortedMap + extends ImmutableSortedMapFauxverideShim implements NavigableMap { + /* + * TODO(kevinb): Confirm that ImmutableSortedMap is faster to construct and + * uses less memory than TreeMap; then say so in the class Javadoc. + */ + private static final Comparator NATURAL_ORDER = Ordering.natural(); + + private static final ImmutableSortedMap NATURAL_EMPTY_MAP = + new EmptyImmutableSortedMap(NATURAL_ORDER); + + static ImmutableSortedMap emptyMap(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return of(); + } else { + return new EmptyImmutableSortedMap(comparator); + } + } + + static ImmutableSortedMap fromSortedEntries( + Comparator comparator, + Collection> entries) { + if (entries.isEmpty()) { + return emptyMap(comparator); + } + + ImmutableList.Builder keyBuilder = ImmutableList.builder(); + ImmutableList.Builder valueBuilder = ImmutableList.builder(); + for (Entry entry : entries) { + keyBuilder.add(entry.getKey()); + valueBuilder.add(entry.getValue()); + } + + return new RegularImmutableSortedMap( + new RegularImmutableSortedSet(keyBuilder.build(), comparator), + valueBuilder.build()); + } + + static ImmutableSortedMap from( + ImmutableSortedSet keySet, ImmutableList valueList) { + if (keySet.isEmpty()) { + return emptyMap(keySet.comparator()); + } else { + return new RegularImmutableSortedMap( + (RegularImmutableSortedSet) keySet, + valueList); + } + } + + /** + * Returns the empty sorted map. + */ + @SuppressWarnings("unchecked") + // unsafe, comparator() returns a comparator on the specified type + // TODO(kevinb): evaluate whether or not of().comparator() should return null + public static ImmutableSortedMap of() { + return (ImmutableSortedMap) NATURAL_EMPTY_MAP; + } + + /** + * Returns an immutable map containing a single entry. + */ + public static , V> + ImmutableSortedMap of(K k1, V v1) { + return from(ImmutableSortedSet.of(k1), ImmutableList.of(v1)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if the two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).build(); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, sorted + * by the natural ordering of the keys. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on a map with keys + * that are not mutually comparable. + * + * @throws ClassCastException if the keys in {@code map} are not mutually + * comparable + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static ImmutableSortedMap copyOf( + Map map) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOfInternal(map, naturalOrder); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, with + * keys sorted by the provided comparator. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the + * comparator + */ + public static ImmutableSortedMap copyOf( + Map map, Comparator comparator) { + return copyOfInternal(map, checkNotNull(comparator)); + } + + /** + * Returns an immutable map containing the same entries as the provided sorted + * map, with the same ordering. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSortedMap copyOfSorted( + SortedMap map) { + Comparator comparator = map.comparator(); + if (comparator == null) { + // If map has a null comparator, the keys should have a natural ordering, + // even though K doesn't explicitly implement Comparable. + comparator = (Comparator) NATURAL_ORDER; + } + return copyOfInternal(map, comparator); + } + + private static ImmutableSortedMap copyOfInternal( + Map map, Comparator comparator) { + boolean sameComparator = false; + if (map instanceof SortedMap) { + SortedMap sortedMap = (SortedMap) map; + Comparator comparator2 = sortedMap.comparator(); + sameComparator = (comparator2 == null) + ? comparator == NATURAL_ORDER + : comparator.equals(comparator2); + } + + if (sameComparator && (map instanceof ImmutableSortedMap)) { + // TODO(kevinb): Prove that this cast is safe, even though + // Collections.unmodifiableSortedMap requires the same key type. + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + + // "adding" type params to an array of a raw type should be safe as + // long as no one can ever cast that same array instance back to a + // raw type. + @SuppressWarnings("unchecked") + Entry[] entries = map.entrySet().toArray(new Entry[0]); + + for (int i = 0; i < entries.length; i++) { + Entry entry = entries[i]; + entries[i] = entryOf(entry.getKey(), entry.getValue()); + } + + List> list = Arrays.asList(entries); + + if (!sameComparator) { + sortEntries(list, comparator); + validateEntries(list, comparator); + } + + return fromSortedEntries(comparator, list); + } + + private static void sortEntries( + List> entries, final Comparator comparator) { + Comparator> entryComparator = new Comparator>() { + + @Override public int compare(Entry entry1, Entry entry2) { + return comparator.compare(entry1.getKey(), entry2.getKey()); + } + }; + + Collections.sort(entries, entryComparator); + } + + private static void validateEntries(List> entries, + Comparator comparator) { + for (int i = 1; i < entries.size(); i++) { + if (comparator.compare( + entries.get(i - 1).getKey(), entries.get(i).getKey()) == 0) { + throw new IllegalArgumentException( + "Duplicate keys in mappings " + entries.get(i - 1) + " and " + + entries.get(i)); + } + } + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are + * ordered by their natural ordering. The sorted maps use {@link + * Ordering#natural()} as the comparator. + * + *

Note: the type parameter {@code K} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static , V> Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * Returns a builder that creates immutable sorted maps with an explicit + * comparator. If the comparator has a more general type than the map's keys, + * such as creating a {@code SortedMap} with a {@code + * Comparator}, use the {@link Builder} constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are + * ordered by the reverse of their natural ordering. + * + *

Note: the type parameter {@code K} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static , V> Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * A builder for creating immutable sorted map instances, especially {@code + * public static final} maps ("constant maps"). Example:

   {@code
+   *
+   *   static final ImmutableSortedMap INT_TO_WORD =
+   *       new ImmutableSortedMap.Builder(Ordering.natural())
+   *           .put(1, "one")
+   *           .put(2, "two")
+   *           .put(3, "three")
+   *           .build();}
+ * + * For small immutable sorted maps, the {@code ImmutableSortedMap.of()} + * methods are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple maps in series. Each map is a superset of + * the maps created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static class Builder extends ImmutableMap.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSortedMap#orderedBy}. + */ + public Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys, according to the comparator (which might be the keys' natural + * order), are not allowed, and will cause {@link #build} to fail. + */ + @Override public Builder put(K key, V value) { + entries.add(entryOf(key, value)); + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if + * necessary. Duplicate keys, according to the comparator (which might be + * the keys' natural order), are not allowed, and will cause {@link #build} + * to fail. + * + * @since 11.0 + */ + @Override public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. + * Duplicate keys, according to the comparator (which might be the keys' + * natural order), are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override public Builder putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Returns a newly-created immutable sorted map. + * + * @throws IllegalArgumentException if any two keys are equal according to + * the comparator (which might be the keys' natural order) + */ + @Override public ImmutableSortedMap build() { + sortEntries(entries, comparator); + validateEntries(entries, comparator); + return fromSortedEntries(comparator, entries); + } + } + + ImmutableSortedMap() { + } + + ImmutableSortedMap(ImmutableSortedMap descendingMap) { + this.descendingMap = descendingMap; + } + + @Override + public int size() { + return values().size(); + } + + @Override public boolean containsValue(@Nullable Object value) { + return values().contains(value); + } + + @Override boolean isPartialView() { + return keySet().isPartialView() || values().isPartialView(); + } + + /** + * Returns an immutable set of the mappings in this map, sorted by the key + * ordering. + */ + @Override public ImmutableSet> entrySet() { + return super.entrySet(); + } + + /** + * Returns an immutable sorted set of the keys in this map. + */ + @Override public abstract ImmutableSortedSet keySet(); + + /** + * Returns an immutable collection of the values in this map, sorted by the + * ordering of the corresponding keys. + */ + @Override public abstract ImmutableCollection values(); + + /** + * Returns the comparator that orders the keys, which is + * {@link Ordering#natural()} when the natural ordering of the keys is used. + * Note that its behavior is not consistent with {@link TreeMap#comparator()}, + * which returns {@code null} to indicate natural ordering. + */ + @Override + public Comparator comparator() { + return keySet().comparator(); + } + + @Override + public K firstKey() { + return keySet().first(); + } + + @Override + public K lastKey() { + return keySet().last(); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are less than {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code toKey} + * greater than an earlier {@code toKey}. However, this method doesn't throw + * an exception in that situation, but instead keeps the original {@code + * toKey}. + */ + @Override + public ImmutableSortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are less than (or equal to, if {@code inclusive}) {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code toKey} + * greater than an earlier {@code toKey}. However, this method doesn't throw + * an exception in that situation, but instead keeps the original {@code + * toKey}. + * + * @since 12.0 + */ + @Override + public abstract ImmutableSortedMap headMap(K toKey, boolean inclusive); + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys ranges from {@code fromKey}, inclusive, to {@code toKey}, + * exclusive. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. Similarly, this method keeps the original {@code toKey}, instead + * of throwing an exception, if passed a {@code toKey} greater than an earlier + * {@code toKey}. + */ + @Override + public ImmutableSortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys ranges from {@code fromKey} to {@code toKey}, inclusive or + * exclusive as indicated by the boolean flags. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. Similarly, this method keeps the original {@code toKey}, instead + * of throwing an exception, if passed a {@code toKey} greater than an earlier + * {@code toKey}. + * + * @since 12.0 + */ + @Override + public ImmutableSortedMap subMap(K fromKey, boolean fromInclusive, K toKey, + boolean toInclusive) { + checkNotNull(fromKey); + checkNotNull(toKey); + checkArgument(comparator().compare(fromKey, toKey) <= 0, + "expected fromKey <= toKey but %s > %s", fromKey, toKey); + return headMap(toKey, toInclusive).tailMap(fromKey, fromInclusive); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are greater than or equals to {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. + */ + @Override + public ImmutableSortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are greater than (or equal to, if {@code inclusive}) + * {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. + * + * @since 12.0 + */ + @Override + public abstract ImmutableSortedMap tailMap(K fromKey, boolean inclusive); + + @Override + public Entry lowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return keyOrNull(lowerEntry(key)); + } + + @Override + public Entry floorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public K floorKey(K key) { + return keyOrNull(floorEntry(key)); + } + + @Override + public Entry ceilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return keyOrNull(ceilingEntry(key)); + } + + @Override + public Entry higherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return keyOrNull(higherEntry(key)); + } + + @Override + public Entry firstEntry() { + return isEmpty() ? null : entrySet().asList().get(0); + } + + @Override + public Entry lastEntry() { + return isEmpty() ? null : entrySet().asList().get(size() - 1); + } + + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + private transient ImmutableSortedMap descendingMap; + + @Override + public ImmutableSortedMap descendingMap() { + ImmutableSortedMap result = descendingMap; + if (result == null) { + result = descendingMap = createDescendingMap(); + } + return result; + } + + abstract ImmutableSortedMap createDescendingMap(); + + @Override + public ImmutableSortedSet navigableKeySet() { + return keySet(); + } + + @Override + public ImmutableSortedSet descendingKeySet() { + return keySet().descendingSet(); + } + + /** + * Serialized type for all ImmutableSortedMap instances. It captures the + * logical contents and they are reconstructed using public factory methods. + * This ensures that the implementation types remain as implementation + * details. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + private final Comparator comparator; + @SuppressWarnings("unchecked") + SerializedForm(ImmutableSortedMap sortedMap) { + super(sortedMap); + comparator = (Comparator) sortedMap.comparator(); + } + @Override Object readResolve() { + Builder builder = new Builder(comparator); + return createMap(builder); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(this); + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java b/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java new file mode 100644 index 0000000..855f223 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * "Overrides" the {@link ImmutableMap} static methods that lack + * {@link ImmutableSortedMap} equivalents with deprecated, exception-throwing + * versions. See {@link ImmutableSortedSetFauxverideShim} for details. + * + * @author Chris Povirk + */ +@GwtCompatible +abstract class ImmutableSortedMapFauxverideShim + extends ImmutableMap { + /** + * Not supported. Use {@link ImmutableSortedMap#naturalOrder}, which offers + * better type-safety, instead. This method exists only to hide + * {@link ImmutableMap#builder} from consumers of {@code ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#naturalOrder}, which offers + * better type-safety. + */ + @Deprecated public static ImmutableSortedMap.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain a + * non-{@code Comparable} key. Proper calls will resolve to the version in + * {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a key of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of(K k1, V v1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls to will resolve to the + * version in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + throw new UnsupportedOperationException(); + } + + // No copyOf() fauxveride; see ImmutableSortedSetFauxverideShim. +} diff --git a/guava/src/com/google/common/collect/ImmutableSortedMultiset.java b/guava/src/com/google/common/collect/ImmutableSortedMultiset.java new file mode 100644 index 0000000..a64e5de --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedMultiset.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * An immutable {@code SortedMultiset} that stores its elements in a sorted array. Some instances + * are ordered by an explicit comparator, while others follow the natural sort ordering of their + * elements. Either way, null elements are not supported. + * + *

Unlike {@link Multisets#unmodifiableSortedMultiset}, which is a view of a separate + * collection that can still change, an instance of {@code ImmutableSortedMultiset} contains its + * own private data and will never change. This class is convenient for {@code public static + * final} multisets ("constant multisets") and also lets you easily make a "defensive copy" of a + * set provided to your class by a caller. + * + *

The multisets returned by the {@link #headMultiset}, {@link #tailMultiset}, and + * {@link #subMultiset} methods share the same array as the original multiset, preventing that + * array from being garbage collected. If this is a concern, the data may be copied into a + * correctly-sized array by calling {@link #copyOfSorted}. + * + *

Note on element equivalence: The {@link #contains(Object)}, + * {@link #containsAll(Collection)}, and {@link #equals(Object)} implementations must check whether + * a provided object is equivalent to an element in the collection. Unlike most collections, an + * {@code ImmutableSortedMultiset} doesn't use {@link Object#equals} to determine if two elements + * are equivalent. Instead, with an explicit comparator, the following relation determines whether + * elements {@code x} and {@code y} are equivalent: + * + *

   {@code
+ *
+ *   {(x, y) | comparator.compare(x, y) == 0}}
+ * + * With natural ordering of elements, the following relation determines whether two elements are + * equivalent: + * + *
   {@code
+ *
+ *   {(x, y) | x.compareTo(y) == 0}}
+ * + * Warning: Like most multisets, an {@code ImmutableSortedMultiset} will not function + * correctly if an element is modified after being placed in the multiset. For this reason, and to + * avoid general confusion, it is strongly recommended to place only immutable objects into this + * collection. + * + *

Note: Although this class is not final, it cannot be subclassed as it has no public or + * protected constructors. Thus, instances of this type are guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Louis Wasserman + * @since 12.0 + */ +@Beta +@GwtIncompatible("hasn't been tested yet") +public abstract class ImmutableSortedMultiset extends ImmutableSortedMultisetFauxverideShim + implements SortedMultiset { + // TODO(user): GWT compatibility + + private static final Comparator NATURAL_ORDER = Ordering.natural(); + + private static final ImmutableSortedMultiset NATURAL_EMPTY_MULTISET = + new EmptyImmutableSortedMultiset(NATURAL_ORDER); + + /** + * Returns the empty immutable sorted multiset. + */ + @SuppressWarnings("unchecked") + public static ImmutableSortedMultiset of() { + return (ImmutableSortedMultiset) NATURAL_EMPTY_MULTISET; + } + + /** + * Returns an immutable sorted multiset containing a single element. + */ + public static > ImmutableSortedMultiset of(E element) { + RegularImmutableSortedSet elementSet = + (RegularImmutableSortedSet) ImmutableSortedSet.of(element); + int[] counts = {1}; + long[] cumulativeCounts = {0, 1}; + return new RegularImmutableSortedMultiset(elementSet, counts, cumulativeCounts, 0, 1); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of(E e1, E e2) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of(E e1, E e2, E e3) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4, e5)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + int size = remaining.length + 6; + List all = Lists.newArrayListWithCapacity(size); + Collections.addAll(all, e1, e2, e3, e4, e5, e6); + Collections.addAll(all, remaining); + return copyOf(Ordering.natural(), all); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static > ImmutableSortedMultiset copyOf(E[] elements) { + return copyOf(Ordering.natural(), Arrays.asList(elements)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. To create a copy of a {@code SortedMultiset} that preserves the + * comparator, call {@link #copyOfSorted} instead. This method iterates over {@code elements} at + * most once. + * + *

Note that if {@code s} is a {@code multiset}, then {@code + * ImmutableSortedMultiset.copyOf(s)} returns an {@code ImmutableSortedMultiset} + * containing each of the strings in {@code s}, while {@code ImmutableSortedMultiset.of(s)} + * returns an {@code ImmutableSortedMultiset>} containing one element (the given + * multiset itself). + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf(Iterable elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedMultisetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf(Iterator elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedMultisetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by the given {@code + * Comparator}. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf( + Comparator comparator, Iterator elements) { + checkNotNull(comparator); + return new Builder(comparator).addAll(elements).build(); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by the given {@code + * Comparator}. This method iterates over {@code elements} at most once. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf( + Comparator comparator, Iterable elements) { + if (elements instanceof ImmutableSortedMultiset) { + @SuppressWarnings("unchecked") // immutable collections are always safe for covariant casts + ImmutableSortedMultiset multiset = (ImmutableSortedMultiset) elements; + if (comparator.equals(multiset.comparator())) { + if (multiset.isPartialView()) { + return copyOfSortedEntries(comparator, multiset.entrySet().asList()); + } else { + return multiset; + } + } + } + elements = Lists.newArrayList(elements); // defensive copy + TreeMultiset sortedCopy = TreeMultiset.create(checkNotNull(comparator)); + Iterables.addAll(sortedCopy, elements); + return copyOfSortedEntries(comparator, sortedCopy.entrySet()); + } + + /** + * Returns an immutable sorted multiset containing the elements of a sorted multiset, sorted by + * the same {@code Comparator}. That behavior differs from {@link #copyOf(Iterable)}, which + * always uses the natural ordering of the elements. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code sortedMultiset} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code sortedMultiset} or any of its elements is null + */ + public static ImmutableSortedMultiset copyOfSorted(SortedMultiset sortedMultiset) { + return copyOfSortedEntries(sortedMultiset.comparator(), + Lists.newArrayList(sortedMultiset.entrySet())); + } + + private static ImmutableSortedMultiset copyOfSortedEntries( + Comparator comparator, Collection> entries) { + if (entries.isEmpty()) { + return emptyMultiset(comparator); + } + ImmutableList.Builder elementsBuilder = new ImmutableList.Builder(entries.size()); + int[] counts = new int[entries.size()]; + long[] cumulativeCounts = new long[entries.size() + 1]; + int i = 0; + for (Entry entry : entries) { + elementsBuilder.add(entry.getElement()); + counts[i] = entry.getCount(); + cumulativeCounts[i + 1] = cumulativeCounts[i] + counts[i]; + i++; + } + return new RegularImmutableSortedMultiset( + new RegularImmutableSortedSet(elementsBuilder.build(), comparator), + counts, cumulativeCounts, 0, entries.size()); + } + + @SuppressWarnings("unchecked") + static ImmutableSortedMultiset emptyMultiset(Comparator comparator) { + if (NATURAL_ORDER.equals(comparator)) { + return (ImmutableSortedMultiset) NATURAL_EMPTY_MULTISET; + } + return new EmptyImmutableSortedMultiset(comparator); + } + + ImmutableSortedMultiset() {} + + @Override + public final Comparator comparator() { + return elementSet().comparator(); + } + + @Override + public abstract ImmutableSortedSet elementSet(); + + transient ImmutableSortedMultiset descendingMultiset; + + @Override + public ImmutableSortedMultiset descendingMultiset() { + ImmutableSortedMultiset result = descendingMultiset; + if (result == null) { + return descendingMultiset = new DescendingImmutableSortedMultiset(this); + } + return result; + } + + /** + * {@inheritDoc} + * + *

This implementation is guaranteed to throw an {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + *

This implementation is guaranteed to throw an {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + */ + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public abstract ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType); + + @Override + public ImmutableSortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + checkArgument(comparator().compare(lowerBound, upperBound) <= 0, + "Expected lowerBound <= upperBound but %s > %s", lowerBound, upperBound); + return tailMultiset(lowerBound, lowerBoundType).headMultiset(upperBound, upperBoundType); + } + + @Override + public abstract ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType); + + /** + * Returns a builder that creates immutable sorted multisets with an explicit comparator. If the + * comparator has a more general type than the set being generated, such as creating a {@code + * SortedMultiset} with a {@code Comparator}, use the {@link Builder} + * constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted multisets whose elements are ordered by the + * reverse of their natural ordering. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather than {@code + * Comparable} as a workaround for javac bug 6468354. + */ + public static > Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * Returns a builder that creates immutable sorted multisets whose elements are ordered by their + * natural ordering. The sorted multisets use {@link Ordering#natural()} as the comparator. This + * method provides more type-safety than {@link #builder}, as it can be called only for classes + * that implement {@link Comparable}. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather than {@code + * Comparable} as a workaround for javac bug 6468354. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable multiset instances, especially {@code public static final} + * multisets ("constant multisets"). Example: + * + *

 {@code
+   *
+   *   public static final ImmutableSortedMultiset BEANS =
+   *       new ImmutableSortedMultiset.Builder()
+   *           .addCopies(Bean.COCOA, 4)
+   *           .addCopies(Bean.GARDEN, 6)
+   *           .addCopies(Bean.RED, 8)
+   *           .addCopies(Bean.BLACK_EYED, 10)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multisets in series. + * + * @since 12.0 + */ + public static class Builder extends ImmutableMultiset.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by + * {@link ImmutableSortedMultiset#orderedBy(Comparator)}. + */ + public Builder(Comparator comparator) { + super(TreeMultiset.create(comparator)); + this.comparator = checkNotNull(comparator); + } + + /** + * Adds {@code element} to the {@code ImmutableSortedMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override + public Builder add(E element) { + super.add(element); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code ImmutableSortedMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May be zero, in which + * case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation + * would result in more than {@link Integer#MAX_VALUE} occurrences of the element + */ + @Override + public Builder addCopies(E element, int occurrences) { + super.addCopies(element, occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + @Override + public Builder setCount(E element, int count) { + super.setCount(element, count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableSortedMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the elements to add to the {@code ImmutableSortedMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedMultiset} based on the contents of the {@code + * Builder}. + */ + @Override + public ImmutableSortedMultiset build() { + return copyOfSorted((SortedMultiset) contents); + } + } + + private static final class SerializedForm implements Serializable { + Comparator comparator; + Object[] elements; + int[] counts; + + SerializedForm(SortedMultiset multiset) { + this.comparator = multiset.comparator(); + int n = multiset.entrySet().size(); + elements = new Object[n]; + counts = new int[n]; + int i = 0; + for (Entry entry : multiset.entrySet()) { + elements[i] = entry.getElement(); + counts[i] = entry.getCount(); + i++; + } + } + + @SuppressWarnings("unchecked") + Object readResolve() { + int n = elements.length; + Builder builder = orderedBy(comparator); + for (int i = 0; i < n; i++) { + builder.addCopies(elements[i], counts[i]); + } + return builder.build(); + } + } + + @Override + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java b/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java new file mode 100644 index 0000000..e7d74c8 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +/** + * "Overrides" the {@link ImmutableMultiset} static methods that lack + * {@link ImmutableSortedMultiset} equivalents with deprecated, exception-throwing versions. This + * prevents accidents like the following: + * + *
   {@code
+ * 
+ *   List objects = ...;
+ *   // Sort them:
+ *   Set sorted = ImmutableSortedMultiset.copyOf(objects);
+ *   // BAD CODE! The returned multiset is actually an unsorted ImmutableMultiset!}
+ *
+ * While we could put the overrides in {@link ImmutableSortedMultiset} itself, it seems clearer to
+ * separate these "do not call" methods from those intended for normal use.
+ *
+ * @author Louis Wasserman
+ */
+abstract class ImmutableSortedMultisetFauxverideShim extends ImmutableMultiset {
+  /**
+   * Not supported. Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better
+   * type-safety, instead. This method exists only to hide {@link ImmutableMultiset#builder} from
+   * consumers of {@code ImmutableSortedMultiset}.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better type-safety.
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset.Builder builder() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass a parameter of type {@code Comparable} to use
+   *             {@link ImmutableSortedMultiset#of(Comparable)}.
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(E element) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use
+   *             {@link ImmutableSortedMultiset#of(Comparable, Comparable)}.
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(E e1, E e2) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use
+   *             {@link ImmutableSortedMultiset#of(Comparable, Comparable, Comparable)}.
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(E e1, E e2, E e3) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *             ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable)}. 
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(E e1, E e2, E e3, E e4) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *             ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable,
+   *             Comparable)} . 
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(E e1, E e2, E e3, E e4, E e5) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain a non-{@code
+   * Comparable} element. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *             ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable,
+   *             Comparable, Comparable, Comparable...)} . 
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset of(
+      E e1,
+      E e2,
+      E e3,
+      E e4,
+      E e5,
+      E e6,
+      E... remaining) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a multiset that may contain non-{@code
+   * Comparable} elements. Proper calls will resolve to the version in {@code
+   * ImmutableSortedMultiset}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass parameters of type {@code Comparable} to use
+   *             {@link ImmutableSortedMultiset#copyOf(Comparable[])}.
+   */
+  @Deprecated
+  public static  ImmutableSortedMultiset copyOf(E[] elements) {
+    throw new UnsupportedOperationException();
+  }
+
+  /*
+   * We would like to include an unsupported " copyOf(Iterable)" here, providing only the
+   * properly typed "> copyOf(Iterable)" in ImmutableSortedMultiset (and
+   * likewise for the Iterator equivalent). However, due to a change in Sun's interpretation of the
+   * JLS (as described at http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler
+   * available as of this writing rejects our attempts. To maintain compatibility with that version
+   * and with any other compilers that interpret the JLS similarly, there is no definition of
+   * copyOf() here, and the definition in ImmutableSortedMultiset matches that in
+   * ImmutableMultiset.
+   *
+   * The result is that ImmutableSortedMultiset.copyOf() may be called on non-Comparable elements.
+   * We have not discovered a better solution. In retrospect, the static factory methods should
+   * have gone in a separate class so that ImmutableSortedMultiset wouldn't "inherit"
+   * too-permissive factory methods from ImmutableMultiset.
+   */
+}
diff --git a/guava/src/com/google/common/collect/ImmutableSortedSet.java b/guava/src/com/google/common/collect/ImmutableSortedSet.java
new file mode 100644
index 0000000..2180e29
--- /dev/null
+++ b/guava/src/com/google/common/collect/ImmutableSortedSet.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright (C) 2008 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.common.collect;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.GwtCompatible;
+import com.google.common.annotations.GwtIncompatible;
+
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NavigableSet;
+import java.util.SortedSet;
+
+import javax.annotation.Nullable;
+
+/**
+ * An immutable {@code SortedSet} that stores its elements in a sorted array.
+ * Some instances are ordered by an explicit comparator, while others follow the
+ * natural sort ordering of their elements. Either way, null elements are not
+ * supported.
+ *
+ * 

Unlike {@link Collections#unmodifiableSortedSet}, which is a view + * of a separate collection that can still change, an instance of {@code + * ImmutableSortedSet} contains its own private data and will never + * change. This class is convenient for {@code public static final} sets + * ("constant sets") and also lets you easily make a "defensive copy" of a set + * provided to your class by a caller. + * + *

The sets returned by the {@link #headSet}, {@link #tailSet}, and + * {@link #subSet} methods share the same array as the original set, preventing + * that array from being garbage collected. If this is a concern, the data may + * be copied into a correctly-sized array by calling {@link #copyOfSorted}. + * + *

Note on element equivalence: The {@link #contains(Object)}, + * {@link #containsAll(Collection)}, and {@link #equals(Object)} + * implementations must check whether a provided object is equivalent to an + * element in the collection. Unlike most collections, an + * {@code ImmutableSortedSet} doesn't use {@link Object#equals} to determine if + * two elements are equivalent. Instead, with an explicit comparator, the + * following relation determines whether elements {@code x} and {@code y} are + * equivalent:

   {@code
+ *
+ *   {(x, y) | comparator.compare(x, y) == 0}}
+ * + * With natural ordering of elements, the following relation determines whether + * two elements are equivalent:
   {@code
+ *
+ *   {(x, y) | x.compareTo(y) == 0}}
+ * + * Warning: Like most sets, an {@code ImmutableSortedSet} will not + * function correctly if an element is modified after being placed in the set. + * For this reason, and to avoid general confusion, it is strongly recommended + * to place only immutable objects into this collection. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @see ImmutableSet + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library; implements {@code NavigableSet} since 12.0) + */ +// TODO(benyu): benchmark and optimize all creation paths, which are a mess now +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSortedSet extends ImmutableSortedSetFauxverideShim + implements NavigableSet, SortedIterable { + + private static final Comparator NATURAL_ORDER = + Ordering.natural(); + + private static final ImmutableSortedSet NATURAL_EMPTY_SET = + new EmptyImmutableSortedSet(NATURAL_ORDER); + + @SuppressWarnings("unchecked") + private static ImmutableSortedSet emptySet() { + return (ImmutableSortedSet) NATURAL_EMPTY_SET; + } + + static ImmutableSortedSet emptySet( + Comparator comparator) { + if (NATURAL_ORDER.equals(comparator)) { + return emptySet(); + } else { + return new EmptyImmutableSortedSet(comparator); + } + } + + /** + * Returns the empty immutable sorted set. + */ + public static ImmutableSortedSet of() { + return emptySet(); + } + + /** + * Returns an immutable sorted set containing a single element. + */ + public static > ImmutableSortedSet of( + E element) { + return new RegularImmutableSortedSet( + ImmutableList.of(element), Ordering.natural()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4, e5)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + * @since 3.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + int size = remaining.length + 6; + List all = new ArrayList(size); + Collections.addAll(all, e1, e2, e3, e4, e5, e6); + Collections.addAll(all, remaining); + return copyOf(Ordering.natural(), all); + } + + // TODO(kevinb): Consider factory methods that reject duplicates + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 3.0 + */ + public static > ImmutableSortedSet copyOf( + E[] elements) { + return copyOf(Ordering.natural(), Arrays.asList(elements)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. To create a + * copy of a {@code SortedSet} that preserves the comparator, call {@link + * #copyOfSorted} instead. This method iterates over {@code elements} at most + * once. + + * + *

Note that if {@code s} is a {@code Set}, then {@code + * ImmutableSortedSet.copyOf(s)} returns an {@code ImmutableSortedSet} + * containing each of the strings in {@code s}, while {@code + * ImmutableSortedSet.of(s)} returns an {@code + * ImmutableSortedSet>} containing one element (the given set + * itself). + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on elements that are + * not mutually comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Iterable elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. To create a + * copy of a {@code SortedSet} that preserves the comparator, call + * {@link #copyOfSorted} instead. This method iterates over {@code elements} + * at most once. + * + *

Note that if {@code s} is a {@code Set}, then + * {@code ImmutableSortedSet.copyOf(s)} returns an + * {@code ImmutableSortedSet} containing each of the strings in + * {@code s}, while {@code ImmutableSortedSet.of(s)} returns an + * {@code ImmutableSortedSet>} containing one element (the given + * set itself). + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + *

This method is not type-safe, as it may be called on elements that are + * not mutually comparable. + * + *

This method is safe to use even when {@code elements} is a synchronized + * or concurrent collection that is currently being modified by another + * thread. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSortedSet copyOf( + Collection elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. + * + *

This method is not type-safe, as it may be called on elements that are + * not mutually comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Iterator elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the given {@code Comparator}. When multiple elements are equivalent + * according to {@code compareTo()}, only the first one specified is + * included. + * + * @throws NullPointerException if {@code comparator} or any of + * {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterator elements) { + return copyOf(comparator, Lists.newArrayList(elements)); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the given {@code Comparator}. When multiple elements are equivalent + * according to {@code compare()}, only the first one specified is + * included. This method iterates over {@code elements} at most once. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + * @throws NullPointerException if {@code comparator} or any of {@code + * elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterable elements) { + checkNotNull(comparator); + boolean hasSameComparator = + SortedIterables.hasSameComparator(comparator, elements); + + if (hasSameComparator && (elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") + ImmutableSortedSet original = (ImmutableSortedSet) elements; + if (!original.isPartialView()) { + return original; + } + } + @SuppressWarnings("unchecked") // elements only contains E's; it's safe. + E[] array = (E[]) Iterables.toArray(elements); + return construct(comparator, array.length, array); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the given {@code Comparator}. When multiple elements are equivalent + * according to {@code compareTo()}, only the first one specified is + * included. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

This method is safe to use even when {@code elements} is a synchronized + * or concurrent collection that is currently being modified by another + * thread. + * + * @throws NullPointerException if {@code comparator} or any of + * {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Collection elements) { + return copyOf(comparator, (Iterable) elements); + } + + /** + * Returns an immutable sorted set containing the elements of a sorted set, + * sorted by the same {@code Comparator}. That behavior differs from {@link + * #copyOf(Iterable)}, which always uses the natural ordering of the + * elements. + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + * + *

This method is safe to use even when {@code sortedSet} is a synchronized + * or concurrent collection that is currently being modified by another + * thread. + * + * @throws NullPointerException if {@code sortedSet} or any of its elements + * is null + */ + public static ImmutableSortedSet copyOfSorted(SortedSet sortedSet) { + Comparator comparator = SortedIterables.comparator(sortedSet); + E[] elements = (E[]) sortedSet.toArray(); + if (elements.length == 0) { + return emptySet(comparator); + } else { + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(elements), comparator); + } + } + + /** + * Sorts and eliminates duplicates from the first {@code n} positions in {@code contents}. + * Returns the number of unique elements. If this returns {@code k}, then the first {@code k} + * elements of {@code contents} will be the sorted, unique elements, and {@code + * contents[i] == null} for {@code k <= i < n}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code contents} is + * null + */ + static int sortAndUnique( + Comparator comparator, int n, E... contents) { + if (n == 0) { + return 0; + } + for (int i = 0; i < n; i++) { + ObjectArrays.checkElementNotNull(contents[i], i); + } + Arrays.sort(contents, 0, n, comparator); + int uniques = 1; + for (int i = 1; i < n; i++) { + E cur = contents[i]; + E prev = contents[uniques - 1]; + if (comparator.compare(cur, prev) != 0) { + contents[uniques++] = cur; + } + } + Arrays.fill(contents, uniques, n, null); + return uniques; + } + + /** + * Constructs an {@code ImmutableSortedSet} from the first {@code n} elements of + * {@code contents}. If {@code k} is the size of the returned {@code ImmutableSortedSet}, then + * the sorted unique elements are in the first {@code k} positions of {@code contents}, and + * {@code contents[i] == null} for {@code k <= i < n}. + * + *

If {@code k == contents.length}, then {@code contents} may no longer be safe for + * modification. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code contents} is + * null + */ + static ImmutableSortedSet construct( + Comparator comparator, int n, E... contents) { + int uniques = sortAndUnique(comparator, n, contents); + if (uniques == 0) { + return emptySet(comparator); + } else if (uniques < contents.length) { + contents = ObjectArrays.arraysCopyOf(contents, uniques); + } + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(contents), comparator); + } + + /** + * Returns a builder that creates immutable sorted sets with an explicit + * comparator. If the comparator has a more general type than the set being + * generated, such as creating a {@code SortedSet} with a + * {@code Comparator}, use the {@link Builder} constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are + * ordered by the reverse of their natural ordering. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static > Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are + * ordered by their natural ordering. The sorted sets use {@link + * Ordering#natural()} as the comparator. This method provides more + * type-safety than {@link #builder}, as it can be called only for classes + * that implement {@link Comparable}. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable sorted set instances, especially {@code + * public static final} sets ("constant sets"), with a given comparator. + * Example:

   {@code
+   *
+   *   public static final ImmutableSortedSet LUCKY_NUMBERS =
+   *       new ImmutableSortedSet.Builder(ODDS_FIRST_COMPARATOR)
+   *           .addAll(SINGLE_DIGIT_PRIMES)
+   *           .add(42)
+   *           .build();}
+ * + * Builder instances can be reused; it is safe to call {@link #build} multiple + * times to build multiple sets in series. Each set is a superset of the set + * created before it. + * + * @since 2.0 (imported from Google Collections Library) + */ + public static final class Builder extends ImmutableSet.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSortedSet#orderedBy}. + */ + public Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Adds {@code element} to the {@code ImmutableSortedSet}. If the + * {@code ImmutableSortedSet} already contains {@code element}, then + * {@code add} has no effect. (only the previously added element + * is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + super.add(element); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedSet} based on the contents + * of the {@code Builder} and its comparator. + */ + @Override public ImmutableSortedSet build() { + @SuppressWarnings("unchecked") // we're careful to put only E's in here + E[] contentsArray = (E[]) contents; + ImmutableSortedSet result = construct(comparator, size, contentsArray); + this.size = result.size(); // we eliminated duplicates in-place in contentsArray + return result; + } + } + + int unsafeCompare(Object a, Object b) { + return unsafeCompare(comparator, a, b); + } + + static int unsafeCompare( + Comparator comparator, Object a, Object b) { + // Pretend the comparator can compare anything. If it turns out it can't + // compare a and b, we should get a CCE on the subsequent line. Only methods + // that are spec'd to throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator = (Comparator) comparator; + return unsafeComparator.compare(a, b); + } + + final transient Comparator comparator; + + ImmutableSortedSet(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Returns the comparator that orders the elements, which is + * {@link Ordering#natural()} when the natural ordering of the + * elements is used. Note that its behavior is not consistent with + * {@link SortedSet#comparator()}, which returns {@code null} to indicate + * natural ordering. + */ + @Override + public Comparator comparator() { + return comparator; + } + + @Override // needed to unify the iterator() methods in Collection and SortedIterable + public abstract UnmodifiableIterator iterator(); + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#headSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code toElement} greater than an earlier {@code toElement}. However, this + * method doesn't throw an exception in that situation, but instead keeps the + * original {@code toElement}. + */ + @Override + public ImmutableSortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public ImmutableSortedSet headSet(E toElement, boolean inclusive) { + return headSetImpl(checkNotNull(toElement), inclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#subSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code fromElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. Similarly, this method keeps the + * original {@code toElement}, instead of throwing an exception, if passed a + * {@code toElement} greater than an earlier {@code toElement}. + */ + @Override + public ImmutableSortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public ImmutableSortedSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator.compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, fromInclusive, toElement, toInclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#tailSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code fromElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. + */ + @Override + public ImmutableSortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public ImmutableSortedSet tailSet(E fromElement, boolean inclusive) { + return tailSetImpl(checkNotNull(fromElement), inclusive); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides + * parameter validation. + */ + abstract ImmutableSortedSet headSetImpl(E toElement, boolean inclusive); + + abstract ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); + + abstract ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive); + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public E lower(E e) { + return Iterables.getFirst(headSet(e, false).descendingSet(), null); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public E floor(E e) { + return Iterables.getFirst(headSet(e, true).descendingSet(), null); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public E ceiling(E e) { + return Iterables.getFirst(tailSet(e, true), null); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public E higher(E e) { + return Iterables.getFirst(tailSet(e, false), null); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public final E pollFirst() { + throw new UnsupportedOperationException(); + } + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public final E pollLast() { + throw new UnsupportedOperationException(); + } + + @GwtIncompatible("NavigableSet") + transient ImmutableSortedSet descendingSet; + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public ImmutableSortedSet descendingSet() { + ImmutableSortedSet result = descendingSet; + if (result == null) { + result = descendingSet = createDescendingSet(); + result.descendingSet = this; + } + return result; + } + + @GwtIncompatible("NavigableSet") + abstract ImmutableSortedSet createDescendingSet(); + + /** + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + @Override + public UnmodifiableIterator descendingIterator() { + return descendingSet().iterator(); + } + + /** + * Returns the position of an element within the set, or -1 if not present. + */ + abstract int indexOf(@Nullable Object target); + + /* + * This class is used to serialize all ImmutableSortedSet instances, + * regardless of implementation type. It captures their "logical contents" + * only. This is necessary to ensure that the existence of a particular + * implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Comparator comparator; + final Object[] elements; + + public SerializedForm(Comparator comparator, Object[] elements) { + this.comparator = comparator; + this.elements = elements; + } + + @SuppressWarnings("unchecked") + Object readResolve() { + return new Builder(comparator).add((E[]) elements).build(); + } + + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm(comparator, toArray()); + } +} + diff --git a/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java b/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java new file mode 100644 index 0000000..302e912 --- /dev/null +++ b/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * "Overrides" the {@link ImmutableSet} static methods that lack + * {@link ImmutableSortedSet} equivalents with deprecated, exception-throwing + * versions. This prevents accidents like the following:

   {@code
+ *
+ *   List objects = ...;
+ *   // Sort them:
+ *   Set sorted = ImmutableSortedSet.copyOf(objects);
+ *   // BAD CODE! The returned set is actually an unsorted ImmutableSet!}
+ *
+ * While we could put the overrides in {@link ImmutableSortedSet} itself, it
+ * seems clearer to separate these "do not call" methods from those intended for
+ * normal use.
+ *
+ * @author Chris Povirk
+ */
+@GwtCompatible
+abstract class ImmutableSortedSetFauxverideShim extends ImmutableSet {
+  /**
+   * Not supported. Use {@link ImmutableSortedSet#naturalOrder}, which offers
+   * better type-safety, instead. This method exists only to hide
+   * {@link ImmutableSet#builder} from consumers of {@code ImmutableSortedSet}.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Use {@link ImmutableSortedSet#naturalOrder}, which offers
+   *     better type-safety.
+   */
+  @Deprecated public static  ImmutableSortedSet.Builder builder() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass a parameter of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(Comparable)}.
+   */
+  @Deprecated public static  ImmutableSortedSet of(E element) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(Comparable, Comparable)}.
+   */
+  @Deprecated public static  ImmutableSortedSet of(E e1, E e2) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(Comparable, Comparable, Comparable)}.
+   */
+  @Deprecated public static  ImmutableSortedSet of(E e1, E e2, E e3) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable)}.
+   * 
+   */
+  @Deprecated public static  ImmutableSortedSet of(
+      E e1, E e2, E e3, E e4) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(
+   *     Comparable, Comparable, Comparable, Comparable, Comparable)}. 
+   */
+  @Deprecated public static  ImmutableSortedSet of(
+      E e1, E e2, E e3, E e4, E e5) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain a
+   * non-{@code Comparable} element. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass the parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable, 
+   *     Comparable, Comparable, Comparable...)}. 
+   */
+  @Deprecated public static  ImmutableSortedSet of(
+      E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Not supported. You are attempting to create a set that may contain
+   * non-{@code Comparable} elements. Proper calls will resolve to the
+   * version in {@code ImmutableSortedSet}, not this dummy version.
+   *
+   * @throws UnsupportedOperationException always
+   * @deprecated Pass parameters of type {@code Comparable} to use {@link
+   *     ImmutableSortedSet#copyOf(Comparable[])}.
+   */
+  @Deprecated public static  ImmutableSortedSet copyOf(E[] elements) {
+    throw new UnsupportedOperationException();
+  }
+
+  /*
+   * We would like to include an unsupported " copyOf(Iterable)" here,
+   * providing only the properly typed
+   * "> copyOf(Iterable)" in ImmutableSortedSet (and
+   * likewise for the Iterator equivalent). However, due to a change in Sun's
+   * interpretation of the JLS (as described at
+   * http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler
+   * available as of this writing rejects our attempts. To maintain
+   * compatibility with that version and with any other compilers that interpret
+   * the JLS similarly, there is no definition of copyOf() here, and the
+   * definition in ImmutableSortedSet matches that in ImmutableSet.
+   * 
+   * The result is that ImmutableSortedSet.copyOf() may be called on
+   * non-Comparable elements. We have not discovered a better solution. In
+   * retrospect, the static factory methods should have gone in a separate class
+   * so that ImmutableSortedSet wouldn't "inherit" too-permissive factory
+   * methods from ImmutableSet.
+   */
+}
diff --git a/guava/src/com/google/common/collect/ImmutableTable.java b/guava/src/com/google/common/collect/ImmutableTable.java
new file mode 100644
index 0000000..331a51e
--- /dev/null
+++ b/guava/src/com/google/common/collect/ImmutableTable.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2009 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.common.collect;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.GwtCompatible;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * An immutable {@link Table} with reliable user-specified iteration order.
+ * Does not permit null keys or values.
+ *
+ * 

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + *

See the Guava User Guide article on + * immutable collections. + * + * @author Gregory Kick + * @since 11.0 + */ +@GwtCompatible +// TODO(gak): make serializable +public abstract class ImmutableTable implements Table { + /** Returns an empty immutable table. */ + @SuppressWarnings("unchecked") + public static final ImmutableTable of() { + return (ImmutableTable) EmptyImmutableTable.INSTANCE; + } + + /** Returns an immutable table containing a single cell. */ + public static final ImmutableTable of(R rowKey, + C columnKey, V value) { + return new SingletonImmutableTable(rowKey, columnKey, value); + } + + /** + * Returns an immutable copy of the provided table. + * + *

The {@link Table#cellSet()} iteration order of the provided table + * determines the iteration ordering of all views in the returned table. Note + * that some views of the original table and the copied table may have + * different iteration orders. For more control over the ordering, create a + * {@link Builder} and call {@link Builder#orderRowsBy}, + * {@link Builder#orderColumnsBy}, and {@link Builder#putAll} + * + *

Despite the method name, this method attempts to avoid actually copying + * the data when it is safe to do so. The exact circumstances under which a + * copy will or will not be performed are undocumented and subject to change. + */ + public static final ImmutableTable copyOf( + Table table) { + if (table instanceof ImmutableTable) { + @SuppressWarnings("unchecked") + ImmutableTable parameterizedTable + = (ImmutableTable) table; + return parameterizedTable; + } else { + int size = table.size(); + switch (size) { + case 0: + return of(); + case 1: + Cell onlyCell + = Iterables.getOnlyElement(table.cellSet()); + return ImmutableTable.of(onlyCell.getRowKey(), + onlyCell.getColumnKey(), onlyCell.getValue()); + default: + ImmutableSet.Builder> cellSetBuilder + = ImmutableSet.builder(); + for (Cell cell : + table.cellSet()) { + /* + * Must cast to be able to create a Cell rather than a + * Cell + */ + cellSetBuilder.add(cellOf((R) cell.getRowKey(), + (C) cell.getColumnKey(), (V) cell.getValue())); + } + return RegularImmutableTable.forCells(cellSetBuilder.build()); + } + } + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder#ImmutableTable.Builder()} constructor. + */ + public static final Builder builder() { + return new Builder(); + } + + /** + * Verifies that {@code rowKey}, {@code columnKey} and {@code value} are + * non-null, and returns a new entry with those values. + */ + static Cell cellOf(R rowKey, C columnKey, V value) { + return Tables.immutableCell(checkNotNull(rowKey), checkNotNull(columnKey), + checkNotNull(value)); + } + + /** + * A builder for creating immutable table instances, especially {@code public + * static final} tables ("constant tables"). Example:

   {@code
+   *
+   *   static final ImmutableTable SPREADSHEET =
+   *       new ImmutableTable.Builder()
+   *           .put(1, 'A', "foo")
+   *           .put(1, 'B', "bar")
+   *           .put(2, 'A', "baz")
+   *           .build();}
+ * + *

By default, the order in which cells are added to the builder determines + * the iteration ordering of all views in the returned table, with {@link + * #putAll} following the {@link Table#cellSet()} iteration order. However, if + * {@link #orderRowsBy} or {@link #orderColumnsBy} is called, the views are + * sorted by the supplied comparators. + * + * For empty or single-cell immutable tables, {@link #of()} and + * {@link #of(Object, Object, Object)} are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple tables in series. Each table is a superset + * of the tables created before it. + * + * @since 11.0 + */ + public static final class Builder { + private final List> cells = Lists.newArrayList(); + private Comparator rowComparator; + private Comparator columnComparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableTable#builder}. + */ + public Builder() {} + + /** + * Specifies the ordering of the generated table's rows. + */ + public Builder orderRowsBy(Comparator rowComparator) { + this.rowComparator = checkNotNull(rowComparator); + return this; + } + + /** + * Specifies the ordering of the generated table's columns. + */ + public Builder orderColumnsBy( + Comparator columnComparator) { + this.columnComparator = checkNotNull(columnComparator); + return this; + } + + /** + * Associates the ({@code rowKey}, {@code columnKey}) pair with {@code + * value} in the built table. Duplicate key pairs are not allowed and will + * cause {@link #build} to fail. + */ + public Builder put(R rowKey, C columnKey, V value) { + cells.add(cellOf(rowKey, columnKey, value)); + return this; + } + + /** + * Adds the given {@code cell} to the table, making it immutable if + * necessary. Duplicate key pairs are not allowed and will cause {@link + * #build} to fail. + */ + public Builder put( + Cell cell) { + if (cell instanceof Tables.ImmutableCell) { + checkNotNull(cell.getRowKey()); + checkNotNull(cell.getColumnKey()); + checkNotNull(cell.getValue()); + @SuppressWarnings("unchecked") // all supported methods are covariant + Cell immutableCell = (Cell) cell; + cells.add(immutableCell); + } else { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + return this; + } + + /** + * Associates all of the given table's keys and values in the built table. + * Duplicate row key column key pairs are not allowed, and will cause + * {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code table} is null + */ + public Builder putAll( + Table table) { + for (Cell cell : table.cellSet()) { + put(cell); + } + return this; + } + + /** + * Returns a newly-created immutable table. + * + * @throws IllegalArgumentException if duplicate key pairs were added + */ + public ImmutableTable build() { + int size = cells.size(); + switch (size) { + case 0: + return of(); + case 1: + return new SingletonImmutableTable( + Iterables.getOnlyElement(cells)); + default: + return RegularImmutableTable.forCells( + cells, rowComparator, columnComparator); + } + } + } + + ImmutableTable() {} + + @Override public abstract ImmutableSet> cellSet(); + + /** + * {@inheritDoc} + * + * @throws NullPointerException if {@code columnKey} is {@code null} + */ + @Override public abstract ImmutableMap column(C columnKey); + + @Override public abstract ImmutableSet columnKeySet(); + + /** + * {@inheritDoc} + * + *

The value {@code Map}s in the returned map are + * {@link ImmutableMap}s as well. + */ + @Override public abstract ImmutableMap> columnMap(); + + /** + * {@inheritDoc} + * + * @throws NullPointerException if {@code rowKey} is {@code null} + */ + @Override public abstract ImmutableMap row(R rowKey); + + @Override public abstract ImmutableSet rowKeySet(); + + /** + * {@inheritDoc} + * + *

The value {@code Map}s in the returned map are + * {@link ImmutableMap}s as well. + */ + @Override public abstract ImmutableMap> rowMap(); + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final V put(R rowKey, C columnKey, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final void putAll( + Table table) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override public int hashCode() { + return cellSet().hashCode(); + } + + @Override public String toString() { + return rowMap().toString(); + } +} diff --git a/guava/src/com/google/common/collect/Interner.java b/guava/src/com/google/common/collect/Interner.java new file mode 100644 index 0000000..cd5e930 --- /dev/null +++ b/guava/src/com/google/common/collect/Interner.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; + +/** + * Provides equivalent behavior to {@link String#intern} for other immutable + * types. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@Beta +public interface Interner { + /** + * Chooses and returns the representative instance for any of a collection of + * instances that are equal to each other. If two {@linkplain Object#equals + * equal} inputs are given to this method, both calls will return the same + * instance. That is, {@code intern(a).equals(a)} always holds, and {@code + * intern(a) == intern(b)} if and only if {@code a.equals(b)}. Note that + * {@code intern(a)} is permitted to return one instance now and a different + * instance later if the original interned instance was garbage-collected. + * + *

Warning: do not use with mutable objects. + * + * @throws NullPointerException if {@code sample} is null + */ + E intern(E sample); +} diff --git a/guava/src/com/google/common/collect/Interners.java b/guava/src/com/google/common/collect/Interners.java new file mode 100644 index 0000000..bd592d6 --- /dev/null +++ b/guava/src/com/google/common/collect/Interners.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.collect.MapMakerInternalMap.ReferenceEntry; + +import java.util.concurrent.ConcurrentMap; + +/** + * Contains static methods pertaining to instances of {@link Interner}. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@Beta +public final class Interners { + private Interners() {} + + /** + * Returns a new thread-safe interner which retains a strong reference to each instance it has + * interned, thus preventing these instances from being garbage-collected. If this retention is + * acceptable, this implementation may perform better than {@link #newWeakInterner}. Note that + * unlike {@link String#intern}, using this interner does not consume memory in the permanent + * generation. + */ + public static Interner newStrongInterner() { + final ConcurrentMap map = new MapMaker().makeMap(); + return new Interner() { + @Override public E intern(E sample) { + E canonical = map.putIfAbsent(checkNotNull(sample), sample); + return (canonical == null) ? sample : canonical; + } + }; + } + + /** + * Returns a new thread-safe interner which retains a weak reference to each instance it has + * interned, and so does not prevent these instances from being garbage-collected. This most + * likely does not perform as well as {@link #newStrongInterner}, but is the best alternative + * when the memory usage of that implementation is unacceptable. Note that unlike {@link + * String#intern}, using this interner does not consume memory in the permanent generation. + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public static Interner newWeakInterner() { + return new WeakInterner(); + } + + private static class WeakInterner implements Interner { + // MapMaker is our friend, we know about this type + private final MapMakerInternalMap map = new MapMaker() + .weakKeys() + .keyEquivalence(Equivalence.equals()) + .makeCustomMap(); + + @Override public E intern(E sample) { + while (true) { + // trying to read the canonical... + ReferenceEntry entry = map.getEntry(sample); + if (entry != null) { + E canonical = entry.getKey(); + if (canonical != null) { // only matters if weak/soft keys are used + return canonical; + } + } + + // didn't see it, trying to put it instead... + Dummy sneaky = map.putIfAbsent(sample, Dummy.VALUE); + if (sneaky == null) { + return sample; + } else { + /* Someone beat us to it! Trying again... + * + * Technically this loop not guaranteed to terminate, so theoretically (extremely + * unlikely) this thread might starve, but even then, there is always going to be another + * thread doing progress here. + */ + } + } + } + + private enum Dummy { VALUE } + } + + /** + * Returns a function that delegates to the {@link Interner#intern} method of the given interner. + * + * @since 8.0 + */ + public static Function asFunction(Interner interner) { + return new InternerFunction(checkNotNull(interner)); + } + + private static class InternerFunction implements Function { + + private final Interner interner; + + public InternerFunction(Interner interner) { + this.interner = interner; + } + + @Override public E apply(E input) { + return interner.intern(input); + } + + @Override public int hashCode() { + return interner.hashCode(); + } + + @Override public boolean equals(Object other) { + if (other instanceof InternerFunction) { + InternerFunction that = (InternerFunction) other; + return interner.equals(that.interner); + } + + return false; + } + } +} diff --git a/guava/src/com/google/common/collect/Iterables.java b/guava/src/com/google/common/collect/Iterables.java new file mode 100644 index 0000000..0dddd6b --- /dev/null +++ b/guava/src/com/google/common/collect/Iterables.java @@ -0,0 +1,1071 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@code Iterable}. Except as noted, each method has a corresponding + * {@link Iterator}-based method in the {@link Iterators} class. + * + *

Performance notes: Unless otherwise noted, all of the iterables + * produced in this class are lazy, which means that their iterators + * only advance the backing iteration when absolutely necessary. + * + *

See the Guava User Guide article on + * {@code Iterables}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Iterables { + private Iterables() {} + + /** Returns an unmodifiable view of {@code iterable}. */ + public static Iterable unmodifiableIterable( + final Iterable iterable) { + checkNotNull(iterable); + if (iterable instanceof UnmodifiableIterable || + iterable instanceof ImmutableCollection) { + return iterable; + } + return new UnmodifiableIterable(iterable); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static Iterable unmodifiableIterable( + ImmutableCollection iterable) { + return checkNotNull(iterable); + } + + private static final class UnmodifiableIterable extends FluentIterable { + private final Iterable iterable; + + private UnmodifiableIterable(Iterable iterable) { + this.iterable = iterable; + } + + @Override + public Iterator iterator() { + return Iterators.unmodifiableIterator(iterable.iterator()); + } + + @Override + public String toString() { + return iterable.toString(); + } + // no equals and hashCode; it would break the contract! + } + + /** + * Returns the number of elements in {@code iterable}. + */ + public static int size(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).size() + : Iterators.size(iterable.iterator()); + } + + /** + * Returns {@code true} if {@code iterable} contains any object for which {@code equals(element)} + * is true. + */ + public static boolean contains(Iterable iterable, @Nullable Object element) + { + if (iterable instanceof Collection) { + Collection collection = (Collection) iterable; + try { + return collection.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + return Iterators.contains(iterable.iterator(), element); + } + + /** + * Removes, from an iterable, every element that belongs to the provided + * collection. + * + *

This method calls {@link Collection#removeAll} if {@code iterable} is a + * collection, and {@link Iterators#removeAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any element was removed from {@code iterable} + */ + public static boolean removeAll( + Iterable removeFrom, Collection elementsToRemove) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).removeAll(checkNotNull(elementsToRemove)) + : Iterators.removeAll(removeFrom.iterator(), elementsToRemove); + } + + /** + * Removes, from an iterable, every element that does not belong to the + * provided collection. + * + *

This method calls {@link Collection#retainAll} if {@code iterable} is a + * collection, and {@link Iterators#retainAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any element was removed from {@code iterable} + */ + public static boolean retainAll( + Iterable removeFrom, Collection elementsToRetain) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).retainAll(checkNotNull(elementsToRetain)) + : Iterators.retainAll(removeFrom.iterator(), elementsToRetain); + } + + /** + * Removes, from an iterable, every element that satisfies the provided + * predicate. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should + * be removed + * @return {@code true} if any elements were removed from the iterable + * + * @throws UnsupportedOperationException if the iterable does not support + * {@code remove()}. + * @since 2.0 + */ + public static boolean removeIf( + Iterable removeFrom, Predicate predicate) { + if (removeFrom instanceof RandomAccess && removeFrom instanceof List) { + return removeIfFromRandomAccessList( + (List) removeFrom, checkNotNull(predicate)); + } + return Iterators.removeIf(removeFrom.iterator(), predicate); + } + + private static boolean removeIfFromRandomAccessList( + List list, Predicate predicate) { + // Note: Not all random access lists support set() so we need to deal with + // those that don't and attempt the slower remove() based solution. + int from = 0; + int to = 0; + + for (; from < list.size(); from++) { + T element = list.get(from); + if (!predicate.apply(element)) { + if (from > to) { + try { + list.set(to, element); + } catch (UnsupportedOperationException e) { + slowRemoveIfForRemainingElements(list, predicate, to, from); + return true; + } + } + to++; + } + } + + // Clear the tail of any remaining items + list.subList(to, list.size()).clear(); + return from != to; + } + + private static void slowRemoveIfForRemainingElements(List list, + Predicate predicate, int to, int from) { + // Here we know that: + // * (to < from) and that both are valid indices. + // * Everything with (index < to) should be kept. + // * Everything with (to <= index < from) should be removed. + // * The element with (index == from) should be kept. + // * Everything with (index > from) has not been checked yet. + + // Check from the end of the list backwards (minimize expected cost of + // moving elements when remove() is called). Stop before 'from' because + // we already know that should be kept. + for (int n = list.size() - 1; n > from; n--) { + if (predicate.apply(list.get(n))) { + list.remove(n); + } + } + // And now remove everything in the range [to, from) (going backwards). + for (int n = from - 1; n >= to; n--) { + list.remove(n); + } + } + + /** + * Determines whether two iterables contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterable1} + * and {@code iterable2} contain the same number of elements and every element + * of {@code iterable1} is equal to the corresponding element of + * {@code iterable2}. + */ + public static boolean elementsEqual( + Iterable iterable1, Iterable iterable2) { + return Iterators.elementsEqual(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Returns a string representation of {@code iterable}, with the format + * {@code [e1, e2, ..., en]}. + */ + public static String toString(Iterable iterable) { + return Iterators.toString(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}. + * + * @throws NoSuchElementException if the iterable is empty + * @throws IllegalArgumentException if the iterable contains multiple + * elements + */ + public static T getOnlyElement(Iterable iterable) { + return Iterators.getOnlyElement(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}, or {@code + * defaultValue} if the iterable is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements + */ + public static T getOnlyElement( + Iterable iterable, @Nullable T defaultValue) { + return Iterators.getOnlyElement(iterable.iterator(), defaultValue); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterable + * have been copied + */ + @GwtIncompatible("Array.newInstance(Class, int)") + public static T[] toArray(Iterable iterable, Class type) { + Collection collection = toCollection(iterable); + T[] array = ObjectArrays.newArray(type, collection.size()); + return collection.toArray(array); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @return a newly-allocated array into which all the elements of the iterable + * have been copied + */ + static Object[] toArray(Iterable iterable) { + return toCollection(iterable).toArray(); + } + + /** + * Converts an iterable into a collection. If the iterable is already a + * collection, it is returned. Otherwise, an {@link java.util.ArrayList} is + * created with the contents of the iterable in the same iteration order. + */ + private static Collection toCollection(Iterable iterable) { + return (iterable instanceof Collection) + ? (Collection) iterable + : Lists.newArrayList(iterable.iterator()); + } + + /** + * Adds all elements in {@code iterable} to {@code collection}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation. + */ + public static boolean addAll( + Collection addTo, Iterable elementsToAdd) { + if (elementsToAdd instanceof Collection) { + Collection c = Collections2.cast(elementsToAdd); + return addTo.addAll(c); + } + return Iterators.addAll(addTo, elementsToAdd.iterator()); + } + + /** + * Returns the number of elements in the specified iterable that equal the + * specified object. This implementation avoids a full iteration when the + * iterable is a {@link Multiset} or {@link Set}. + * + * @see Collections#frequency + */ + public static int frequency(Iterable iterable, @Nullable Object element) { + if ((iterable instanceof Multiset)) { + return ((Multiset) iterable).count(element); + } + if ((iterable instanceof Set)) { + return ((Set) iterable).contains(element) ? 1 : 0; + } + return Iterators.frequency(iterable.iterator(), element); + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the elements of + * {@code iterable}. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + *

To cycle over the iterable {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, iterable))} + */ + public static Iterable cycle(final Iterable iterable) { + checkNotNull(iterable); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.cycle(iterable); + } + @Override public String toString() { + return iterable.toString() + " (cycled)"; + } + }; + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the provided + * elements. + * + *

After {@code remove} is invoked on a generated iterator, the removed + * element will no longer appear in either that iterator or any other iterator + * created from the same source iterable. That is, this method behaves exactly + * as {@code Iterables.cycle(Lists.newArrayList(elements))}. The iterator's + * {@code hasNext} method returns {@code true} until all of the original + * elements have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + *

To cycle over the elements {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, Arrays.asList(elements)))} + */ + public static Iterable cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterables into a single iterable. The returned iterable has an + * iterator that traverses the elements in {@code a}, followed by the elements + * in {@code b}. The source iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat( + Iterable a, Iterable b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b)); + } + + /** + * Combines three iterables into a single iterable. The returned iterable has + * an iterator that traverses the elements in {@code a}, followed by the + * elements in {@code b}, followed by the elements in {@code c}. The source + * iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat(Iterable a, + Iterable b, Iterable c) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + return concat(Arrays.asList(a, b, c)); + } + + /** + * Combines four iterables into a single iterable. The returned iterable has + * an iterator that traverses the elements in {@code a}, followed by the + * elements in {@code b}, followed by the elements in {@code c}, followed by + * the elements in {@code d}. The source iterators are not polled until + * necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat(Iterable a, + Iterable b, Iterable c, + Iterable d) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + checkNotNull(d); + return concat(Arrays.asList(a, b, c, d)); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @throws NullPointerException if any of the provided iterables is null + */ + public static Iterable concat(Iterable... inputs) { + return concat(ImmutableList.copyOf(inputs)); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. The methods of the returned + * iterable may throw {@code NullPointerException} if any of the input + * iterators is null. + */ + public static Iterable concat( + final Iterable> inputs) { + checkNotNull(inputs); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.concat(iterators(inputs)); + } + }; + } + + /** + * Returns an iterator over the iterators of the given iterables. + */ + private static UnmodifiableIterator> iterators( + Iterable> iterables) { + final Iterator> iterableIterator = + iterables.iterator(); + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterableIterator.hasNext(); + } + @Override + public Iterator next() { + return iterableIterator.next().iterator(); + } + }; + } + + /** + * Divides an iterable into unmodifiable sublists of the given size (the final + * iterable may be smaller). For example, partitioning an iterable containing + * {@code [a, b, c, d, e]} with a partition size of 3 yields {@code + * [[a, b, c], [d, e]]} -- an outer iterable containing two inner lists of + * three and two elements, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link + * Iterator#remove()} method. The returned lists implement {@link + * RandomAccess}, whether or not the input list does. + * + *

Note: if {@code iterable} is a {@link List}, use {@link + * Lists#partition(List, int)} instead. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterable of unmodifiable lists containing the elements of {@code + * iterable} divided into partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> partition( + final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new FluentIterable>() { + @Override + public Iterator> iterator() { + return Iterators.partition(iterable.iterator(), size); + } + }; + } + + /** + * Divides an iterable into unmodifiable sublists of the given size, padding + * the final iterable with null values if necessary. For example, partitioning + * an iterable containing {@code [a, b, c, d, e]} with a partition size of 3 + * yields {@code [[a, b, c], [d, e, null]]} -- an outer iterable containing + * two inner lists of three elements each, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link + * Iterator#remove()} method. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition + * @return an iterable of unmodifiable lists containing the elements of {@code + * iterable} divided into partitions (the final iterable may have + * trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> paddedPartition( + final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new FluentIterable>() { + @Override + public Iterator> iterator() { + return Iterators.paddedPartition(iterable.iterator(), size); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * resulting iterable's iterator does not support {@code remove()}. + */ + public static Iterable filter( + final Iterable unfiltered, final Predicate predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterable has elements whose class is {@code type} or a subclass of + * {@code type}. The returned iterable's iterator does not support + * {@code remove()}. + * + * @param unfiltered an iterable containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterable containing all elements of the original + * iterable that were of the requested type + */ + @GwtIncompatible("Class.isInstance") + public static Iterable filter( + final Iterable unfiltered, final Class type) { + checkNotNull(unfiltered); + checkNotNull(type); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), type); + } + }; + } + + /** + * Returns {@code true} if any element in {@code iterable} satisfies the predicate. + */ + public static boolean any( + Iterable iterable, Predicate predicate) { + return Iterators.any(iterable.iterator(), predicate); + } + + /** + * Returns {@code true} if every element in {@code iterable} satisfies the + * predicate. If {@code iterable} is empty, {@code true} is returned. + */ + public static boolean all( + Iterable iterable, Predicate predicate) { + return Iterators.all(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given + * predicate; use this method only when such an element is known to exist. If + * it is possible that no element will match, use {@link #tryFind} or + * {@link #find(Iterable, Predicate, Object)} instead. + * + * @throws NoSuchElementException if no element in {@code iterable} matches + * the given predicate + */ + public static T find(Iterable iterable, + Predicate predicate) { + return Iterators.find(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given + * predicate, or {@code defaultValue} if none found. Note that this can + * usually be handled more naturally using {@code + * tryFind(iterable, predicate).or(defaultValue)}. + * + * @since 7.0 + */ + public static T find(Iterable iterable, + Predicate predicate, @Nullable T defaultValue) { + return Iterators.find(iterable.iterator(), predicate, defaultValue); + } + + /** + * Returns an {@link Optional} containing the first element in {@code + * iterable} that satisfies the given predicate, if such an element exists. + * + *

Warning: avoid using a {@code predicate} that matches {@code + * null}. If {@code null} is matched in {@code iterable}, a + * NullPointerException will be thrown. + * + * @since 11.0 + */ + public static Optional tryFind(Iterable iterable, + Predicate predicate) { + return Iterators.tryFind(iterable.iterator(), predicate); + } + + /** + * Returns the index in {@code iterable} of the first element that satisfies + * the provided {@code predicate}, or {@code -1} if the Iterable has no such + * elements. + * + *

More formally, returns the lowest index {@code i} such that + * {@code predicate.apply(Iterables.get(iterable, i))} returns {@code true}, + * or {@code -1} if there is no such index. + * + * @since 2.0 + */ + public static int indexOf( + Iterable iterable, Predicate predicate) { + return Iterators.indexOf(iterable.iterator(), predicate); + } + + /** + * Returns an iterable that applies {@code function} to each element of {@code + * fromIterable}. + * + *

The returned iterable's iterator supports {@code remove()} if the + * provided iterator does. After a successful {@code remove()} call, + * {@code fromIterable} no longer contains the corresponding element. + * + *

If the input {@code Iterable} is known to be a {@code List} or other + * {@code Collection}, consider {@link Lists#transform} and {@link + * Collections2#transform}. + */ + public static Iterable transform(final Iterable fromIterable, + final Function function) { + checkNotNull(fromIterable); + checkNotNull(function); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.transform(fromIterable.iterator(), function); + } + }; + } + + /** + * Returns the element at the specified position in an iterable. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterable} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the size of {@code iterable} + */ + public static T get(Iterable iterable, int position) { + checkNotNull(iterable); + if (iterable instanceof List) { + return ((List) iterable).get(position); + } + + if (iterable instanceof Collection) { + // Can check both ends + Collection collection = (Collection) iterable; + Preconditions.checkElementIndex(position, collection.size()); + } else { + // Can only check the lower end + checkNonnegativeIndex(position); + } + return Iterators.get(iterable.iterator(), position); + } + + private static void checkNonnegativeIndex(int position) { + if (position < 0) { + throw new IndexOutOfBoundsException( + "position cannot be negative: " + position); + } + } + + /** + * Returns the element at the specified position in an iterable or a default + * value otherwise. + * + * @param position position of the element to return + * @param defaultValue the default value to return if {@code position} is + * greater than or equal to the size of the iterable + * @return the element at the specified position in {@code iterable} or + * {@code defaultValue} if {@code iterable} contains fewer than + * {@code position + 1} elements. + * @throws IndexOutOfBoundsException if {@code position} is negative + * @since 4.0 + */ + public static T get(Iterable iterable, int position, @Nullable T defaultValue) { + checkNotNull(iterable); + checkNonnegativeIndex(position); + + try { + return get(iterable, position); + } catch (IndexOutOfBoundsException e) { + return defaultValue; + } + } + + /** + * Returns the first element in {@code iterable} or {@code defaultValue} if + * the iterable is empty. The {@link Iterators} analog to this method is + * {@link Iterators#getNext}. + * + * @param defaultValue the default value to return if the iterable is empty + * @return the first element of {@code iterable} or the default value + * @since 7.0 + */ + public static T getFirst(Iterable iterable, @Nullable T defaultValue) { + return Iterators.getNext(iterable.iterator(), defaultValue); + } + + /** + * Returns the last element of {@code iterable}. + * + * @return the last element of {@code iterable} + * @throws NoSuchElementException if the iterable is empty + */ + public static T getLast(Iterable iterable) { + // TODO(kevinb): Support a concurrently modified collection? + if (iterable instanceof List) { + List list = (List) iterable; + if (list.isEmpty()) { + throw new NoSuchElementException(); + } + return getLastInNonemptyList(list); + } + + /* + * TODO(kevinb): consider whether this "optimization" is worthwhile. Users + * with SortedSets tend to know they are SortedSets and probably would not + * call this method. + */ + if (iterable instanceof SortedSet) { + SortedSet sortedSet = (SortedSet) iterable; + return sortedSet.last(); + } + + return Iterators.getLast(iterable.iterator()); + } + + /** + * Returns the last element of {@code iterable} or {@code defaultValue} if + * the iterable is empty. + * + * @param defaultValue the value to return if {@code iterable} is empty + * @return the last element of {@code iterable} or the default value + * @since 3.0 + */ + public static T getLast(Iterable iterable, @Nullable T defaultValue) { + if (iterable instanceof Collection) { + Collection collection = Collections2.cast(iterable); + if (collection.isEmpty()) { + return defaultValue; + } + } + + if (iterable instanceof List) { + List list = Lists.cast(iterable); + return getLastInNonemptyList(list); + } + + /* + * TODO(kevinb): consider whether this "optimization" is worthwhile. Users + * with SortedSets tend to know they are SortedSets and probably would not + * call this method. + */ + if (iterable instanceof SortedSet) { + SortedSet sortedSet = Sets.cast(iterable); + return sortedSet.last(); + } + + return Iterators.getLast(iterable.iterator(), defaultValue); + } + + private static T getLastInNonemptyList(List list) { + return list.get(list.size() - 1); + } + + /** + * Returns a view of {@code iterable} that skips its first + * {@code numberToSkip} elements. If {@code iterable} contains fewer than + * {@code numberToSkip} elements, the returned iterable skips all of its + * elements. + * + *

Modifications to the underlying {@link Iterable} before a call to + * {@code iterator()} are reflected in the returned iterator. That is, the + * iterator skips the first {@code numberToSkip} elements that exist when the + * {@code Iterator} is created, not when {@code skip()} is called. + * + *

The returned iterable's iterator supports {@code remove()} if the + * iterator of the underlying iterable supports it. Note that it is + * not possible to delete the last skipped element by immediately + * calling {@code remove()} on that iterator, as the {@code Iterator} + * contract states that a call to {@code remove()} before a call to + * {@code next()} will throw an {@link IllegalStateException}. + * + * @since 3.0 + */ + public static Iterable skip(final Iterable iterable, + final int numberToSkip) { + checkNotNull(iterable); + checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); + + if (iterable instanceof List) { + final List list = (List) iterable; + return new FluentIterable() { + @Override + public Iterator iterator() { + // TODO(kevinb): Support a concurrently modified collection? + return (numberToSkip >= list.size()) + ? Iterators.emptyIterator() + : list.subList(numberToSkip, list.size()).iterator(); + } + }; + } + + return new FluentIterable() { + @Override + public Iterator iterator() { + final Iterator iterator = iterable.iterator(); + + Iterators.advance(iterator, numberToSkip); + + /* + * We can't just return the iterator because an immediate call to its + * remove() method would remove one of the skipped elements instead of + * throwing an IllegalStateException. + */ + return new Iterator() { + boolean atStart = true; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + try { + return iterator.next(); + } finally { + atStart = false; + } + } + + @Override + public void remove() { + if (atStart) { + throw new IllegalStateException(); + } + iterator.remove(); + } + }; + } + }; + } + + /** + * Creates an iterable with the first {@code limitSize} elements of the given + * iterable. If the original iterable does not contain that many elements, the + * returned iterator will have the same behavior as the original iterable. The + * returned iterable's iterator supports {@code remove()} if the original + * iterator does. + * + * @param iterable the iterable to limit + * @param limitSize the maximum number of elements in the returned iterator + * @throws IllegalArgumentException if {@code limitSize} is negative + * @since 3.0 + */ + public static Iterable limit( + final Iterable iterable, final int limitSize) { + checkNotNull(iterable); + checkArgument(limitSize >= 0, "limit is negative"); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.limit(iterable.iterator(), limitSize); + } + }; + } + + /** + * Returns a view of the supplied iterable that wraps each generated + * {@link Iterator} through {@link Iterators#consumingIterator(Iterator)}. + * + *

Note: If {@code iterable} is a {@link Queue}, the returned iterable will + * get entries from {@link Queue#remove()} since {@link Queue}'s iteration + * order is undefined. Calling {@link Iterator#hasNext()} on a generated + * iterator from the returned iterable may cause an item to be immediately + * dequeued for return on a subsequent call to {@link Iterator#next()}. + * + * @param iterable the iterable to wrap + * @return a view of the supplied iterable that wraps each generated iterator + * through {@link Iterators#consumingIterator(Iterator)}; for queues, + * an iterable that generates iterators that return and consume the + * queue's elements in queue order + * + * @see Iterators#consumingIterator(Iterator) + * @since 2.0 + */ + public static Iterable consumingIterable(final Iterable iterable) { + if (iterable instanceof Queue) { + return new FluentIterable() { + @Override + public Iterator iterator() { + return new ConsumingQueueIterator((Queue) iterable); + } + }; + } + + checkNotNull(iterable); + + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.consumingIterator(iterable.iterator()); + } + }; + } + + private static class ConsumingQueueIterator extends AbstractIterator { + private final Queue queue; + + private ConsumingQueueIterator(Queue queue) { + this.queue = queue; + } + + @Override public T computeNext() { + try { + return queue.remove(); + } catch (NoSuchElementException e) { + return endOfData(); + } + } + } + + // Methods only in Iterables, not in Iterators + + /** + * Determines if the given iterable contains no elements. + * + *

There is no precise {@link Iterator} equivalent to this method, since + * one can only ask an iterator whether it has any elements remaining + * (which one does using {@link Iterator#hasNext}). + * + * @return {@code true} if the iterable contains no elements + */ + public static boolean isEmpty(Iterable iterable) { + if (iterable instanceof Collection) { + return ((Collection) iterable).isEmpty(); + } + return !iterable.iterator().hasNext(); + } + + /** + * Returns an iterable over the merged contents of all given + * {@code iterables}. Equivalent entries will not be de-duplicated. + * + *

Callers must ensure that the source {@code iterables} are in + * non-descending order as this method does not sort its input. + * + *

For any equivalent elements across all {@code iterables}, it is + * undefined which element is returned first. + * + * @since 11.0 + */ + @Beta + public static Iterable mergeSorted( + final Iterable> iterables, + final Comparator comparator) { + checkNotNull(iterables, "iterables"); + checkNotNull(comparator, "comparator"); + Iterable iterable = new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.mergeSorted( + Iterables.transform(iterables, Iterables.toIterator()), + comparator); + } + }; + return new UnmodifiableIterable(iterable); + } + + // TODO(user): Is this the best place for this? Move to fluent functions? + // Useful as a public method? + private static Function, Iterator> + toIterator() { + return new Function, Iterator>() { + @Override + public Iterator apply(Iterable iterable) { + return iterable.iterator(); + } + }; + } +} diff --git a/guava/src/com/google/common/collect/Iterators.java b/guava/src/com/google/common/collect/Iterators.java new file mode 100644 index 0000000..39e60dd --- /dev/null +++ b/guava/src/com/google/common/collect/Iterators.java @@ -0,0 +1,1427 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +import javax.annotation.Nullable; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@link Iterator}. Except as noted, each method has a corresponding + * {@link Iterable}-based method in the {@link Iterables} class. + * + *

Performance notes: Unless otherwise noted, all of the iterators + * produced in this class are lazy, which means that they only advance + * the backing iteration when absolutely necessary. + * + *

See the Guava User Guide section on + * {@code Iterators}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Iterators { + private Iterators() {} + + static final UnmodifiableListIterator EMPTY_LIST_ITERATOR + = new UnmodifiableListIterator() { + @Override + public boolean hasNext() { + return false; + } + @Override + public Object next() { + throw new NoSuchElementException(); + } + @Override + public boolean hasPrevious() { + return false; + } + @Override + public Object previous() { + throw new NoSuchElementException(); + } + @Override + public int nextIndex() { + return 0; + } + @Override + public int previousIndex() { + return -1; + } + }; + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link + * ImmutableSet#of()}. + */ + public static UnmodifiableIterator emptyIterator() { + return emptyListIterator(); + } + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link + * ImmutableSet#of()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static UnmodifiableListIterator emptyListIterator() { + return (UnmodifiableListIterator) EMPTY_LIST_ITERATOR; + } + + private static final Iterator EMPTY_MODIFIABLE_ITERATOR = + new Iterator() { + @Override public boolean hasNext() { + return false; + } + + @Override public Object next() { + throw new NoSuchElementException(); + } + + @Override public void remove() { + throw new IllegalStateException(); + } + }; + + /** + * Returns the empty {@code Iterator} that throws + * {@link IllegalStateException} instead of + * {@link UnsupportedOperationException} on a call to + * {@link Iterator#remove()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static Iterator emptyModifiableIterator() { + return (Iterator) EMPTY_MODIFIABLE_ITERATOR; + } + + /** Returns an unmodifiable view of {@code iterator}. */ + public static UnmodifiableIterator unmodifiableIterator( + final Iterator iterator) { + checkNotNull(iterator); + if (iterator instanceof UnmodifiableIterator) { + return (UnmodifiableIterator) iterator; + } + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public T next() { + return iterator.next(); + } + }; + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static UnmodifiableIterator unmodifiableIterator( + UnmodifiableIterator iterator) { + return checkNotNull(iterator); + } + + /** Returns an unmodifiable view of {@code iterator}. */ + static UnmodifiableListIterator unmodifiableListIterator( + final ListIterator iterator) { + checkNotNull(iterator); + if (iterator instanceof UnmodifiableListIterator) { + return (UnmodifiableListIterator) iterator; + } + return new UnmodifiableListIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + @Override + public T next() { + return iterator.next(); + } + @Override + public T previous() { + return iterator.previous(); + } + @Override + public int nextIndex() { + return iterator.nextIndex(); + } + @Override + public int previousIndex() { + return iterator.previousIndex(); + } + }; + } + + /** + * Returns the number of elements remaining in {@code iterator}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + */ + public static int size(Iterator iterator) { + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } + + /** + * Returns {@code true} if {@code iterator} contains {@code element}. + */ + public static boolean contains(Iterator iterator, @Nullable Object element) + { + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + return true; + } + } + } + return false; + } + + /** + * Traverses an iterator and removes every element that belongs to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any element was removed from {@code iterator} + */ + public static boolean removeAll( + Iterator removeFrom, Collection elementsToRemove) { + checkNotNull(elementsToRemove); + boolean modified = false; + while (removeFrom.hasNext()) { + if (elementsToRemove.contains(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes every element that satisfies the provided predicate from the + * iterator. The iterator will be left exhausted: its {@code hasNext()} + * method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should + * be removed + * @return {@code true} if any elements were removed from the iterator + * @since 2.0 + */ + public static boolean removeIf( + Iterator removeFrom, Predicate predicate) { + checkNotNull(predicate); + boolean modified = false; + while (removeFrom.hasNext()) { + if (predicate.apply(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Traverses an iterator and removes every element that does not belong to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any element was removed from {@code iterator} + */ + public static boolean retainAll( + Iterator removeFrom, Collection elementsToRetain) { + checkNotNull(elementsToRetain); + boolean modified = false; + while (removeFrom.hasNext()) { + if (!elementsToRetain.contains(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Determines whether two iterators contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterator1} + * and {@code iterator2} contain the same number of elements and every element + * of {@code iterator1} is equal to the corresponding element of + * {@code iterator2}. + * + *

Note that this will modify the supplied iterators, since they will have + * been advanced some number of elements forward. + */ + public static boolean elementsEqual( + Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + Object o1 = iterator1.next(); + Object o2 = iterator2.next(); + if (!Objects.equal(o1, o2)) { + return false; + } + } + return !iterator2.hasNext(); + } + + /** + * Returns a string representation of {@code iterator}, with the format + * {@code [e1, e2, ..., en]}. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + */ + public static String toString(Iterator iterator) { + return Joiner.on(", ") + .useForNull("null") + .appendTo(new StringBuilder().append('['), iterator) + .append(']') + .toString(); + } + + /** + * Returns the single element contained in {@code iterator}. + * + * @throws NoSuchElementException if the iterator is empty + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static T getOnlyElement(Iterator iterator) { + T first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + + StringBuilder sb = new StringBuilder(); + sb.append("expected one element but was: <" + first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", " + iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append('>'); + + throw new IllegalArgumentException(sb.toString()); + } + + /** + * Returns the single element contained in {@code iterator}, or {@code + * defaultValue} if the iterator is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static T getOnlyElement(Iterator iterator, @Nullable T defaultValue) { + return iterator.hasNext() ? getOnlyElement(iterator) : defaultValue; + } + + /** + * Copies an iterator's elements into an array. The iterator will be left + * exhausted: its {@code hasNext()} method will return {@code false}. + * + * @param iterator the iterator to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterator + * have been copied + */ + @GwtIncompatible("Array.newInstance(Class, int)") + public static T[] toArray( + Iterator iterator, Class type) { + List list = Lists.newArrayList(iterator); + return Iterables.toArray(list, type); + } + + /** + * Adds all elements in {@code iterator} to {@code collection}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation + */ + public static boolean addAll( + Collection addTo, Iterator iterator) { + checkNotNull(addTo); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= addTo.add(iterator.next()); + } + return wasModified; + } + + /** + * Returns the number of elements in the specified iterator that equal the + * specified object. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @see Collections#frequency + */ + public static int frequency(Iterator iterator, @Nullable Object element) { + int result = 0; + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + result++; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + result++; + } + } + } + return result; + } + + /** + * Returns an iterator that cycles indefinitely over the elements of {@code + * iterable}. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static Iterator cycle(final Iterable iterable) { + checkNotNull(iterable); + return new Iterator() { + Iterator iterator = emptyIterator(); + Iterator removeFrom; + + @Override + public boolean hasNext() { + if (!iterator.hasNext()) { + iterator = iterable.iterator(); + } + return iterator.hasNext(); + } + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = iterator; + return iterator.next(); + } + @Override + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Returns an iterator that cycles indefinitely over the provided elements. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, but {@code elements} does not change. The iterator's + * {@code hasNext()} method returns {@code true} until all of the original + * elements have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static Iterator cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}. The source iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + *

Note: the current implementation is not suitable for nested + * concatenated iterators, i.e. the following should be avoided when in a loop: + * {@code iterator = Iterators.concat(iterator, suffix);}, since iteration over the + * resulting iterator has a cubic complexity to the depth of the nesting. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b).iterator()); + } + + /** + * Combines three iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}, followed by the elements in {@code c}. The source iterators + * are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + *

Note: the current implementation is not suitable for nested + * concatenated iterators, i.e. the following should be avoided when in a loop: + * {@code iterator = Iterators.concat(iterator, suffix);}, since iteration over the + * resulting iterator has a cubic complexity to the depth of the nesting. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b, Iterator c) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + return concat(Arrays.asList(a, b, c).iterator()); + } + + /** + * Combines four iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}, followed by the elements in {@code c}, followed by the elements + * in {@code d}. The source iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + *

Note: the current implementation is not suitable for nested + * concatenated iterators, i.e. the following should be avoided when in a loop: + * {@code iterator = Iterators.concat(iterator, suffix);}, since iteration over the + * resulting iterator has a cubic complexity to the depth of the nesting. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b, Iterator c, + Iterator d) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + checkNotNull(d); + return concat(Arrays.asList(a, b, c, d).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + *

Note: the current implementation is not suitable for nested + * concatenated iterators, i.e. the following should be avoided when in a loop: + * {@code iterator = Iterators.concat(iterator, suffix);}, since iteration over the + * resulting iterator has a cubic complexity to the depth of the nesting. + * + * @throws NullPointerException if any of the provided iterators is null + */ + public static Iterator concat(Iterator... inputs) { + return concat(ImmutableList.copyOf(inputs).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. The methods of the returned iterator may throw + * {@code NullPointerException} if any of the input iterators is null. + * + *

Note: the current implementation is not suitable for nested + * concatenated iterators, i.e. the following should be avoided when in a loop: + * {@code iterator = Iterators.concat(iterator, suffix);}, since iteration over the + * resulting iterator has a cubic complexity to the depth of the nesting. + */ + public static Iterator concat( + final Iterator> inputs) { + checkNotNull(inputs); + return new Iterator() { + Iterator current = emptyIterator(); + Iterator removeFrom; + + @Override + public boolean hasNext() { + // http://code.google.com/p/google-collections/issues/detail?id=151 + // current.hasNext() might be relatively expensive, worth minimizing. + boolean currentHasNext; + // checkNotNull eager for GWT + // note: it must be here & not where 'current' is assigned, + // because otherwise we'll have called inputs.next() before throwing + // the first NPE, and the next time around we'll call inputs.next() + // again, incorrectly moving beyond the error. + while (!(currentHasNext = checkNotNull(current).hasNext()) + && inputs.hasNext()) { + current = inputs.next(); + } + return currentHasNext; + } + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = current; + return current.next(); + } + @Override + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Divides an iterator into unmodifiable sublists of the given size (the final + * list may be smaller). For example, partitioning an iterator containing + * {@code [a, b, c, d, e]} with a partition size of 3 yields {@code + * [[a, b, c], [d, e]]} -- an outer iterator containing two inner lists of + * three and two elements, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterator of immutable lists containing the elements of {@code + * iterator} divided into partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> partition( + Iterator iterator, int size) { + return partitionImpl(iterator, size, false); + } + + /** + * Divides an iterator into unmodifiable sublists of the given size, padding + * the final iterator with null values if necessary. For example, partitioning + * an iterator containing {@code [a, b, c, d, e]} with a partition size of 3 + * yields {@code [[a, b, c], [d, e, null]]} -- an outer iterator containing + * two inner lists of three elements each, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition + * @return an iterator of immutable lists containing the elements of {@code + * iterator} divided into partitions (the final iterable may have + * trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> paddedPartition( + Iterator iterator, int size) { + return partitionImpl(iterator, size, true); + } + + private static UnmodifiableIterator> partitionImpl( + final Iterator iterator, final int size, final boolean pad) { + checkNotNull(iterator); + checkArgument(size > 0); + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public List next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Object[] array = new Object[size]; + int count = 0; + for (; count < size && iterator.hasNext(); count++) { + array[count] = iterator.next(); + } + for (int i = count; i < size; i++) { + array[i] = null; // for GWT + } + + @SuppressWarnings("unchecked") // we only put Ts in it + List list = Collections.unmodifiableList( + (List) Arrays.asList(array)); + return (pad || count == size) ? list : list.subList(0, count); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. + */ + public static UnmodifiableIterator filter( + final Iterator unfiltered, final Predicate predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new AbstractIterator() { + @Override protected T computeNext() { + while (unfiltered.hasNext()) { + T element = unfiltered.next(); + if (predicate.apply(element)) { + return element; + } + } + return endOfData(); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterator has elements whose class is {@code type} or a subclass of + * {@code type}. + * + * @param unfiltered an iterator containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterator containing all elements of the original + * iterator that were of the requested type + */ + @SuppressWarnings("unchecked") // can cast to because non-Ts are removed + @GwtIncompatible("Class.isInstance") + public static UnmodifiableIterator filter( + Iterator unfiltered, Class type) { + return (UnmodifiableIterator) + filter(unfiltered, Predicates.instanceOf(type)); + } + + /** + * Returns {@code true} if one or more elements returned by {@code iterator} + * satisfy the given predicate. + */ + public static boolean any( + Iterator iterator, Predicate predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element)) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if every element returned by {@code iterator} + * satisfies the given predicate. If {@code iterator} is empty, {@code true} + * is returned. + */ + public static boolean all( + Iterator iterator, Predicate predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (!predicate.apply(element)) { + return false; + } + } + return true; + } + + /** + * Returns the first element in {@code iterator} that satisfies the given + * predicate; use this method only when such an element is known to exist. If + * no such element is found, the iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. If it is possible that + * no element will match, use {@link #tryFind} or {@link + * #find(Iterator, Predicate, Object)} instead. + * + * @throws NoSuchElementException if no element in {@code iterator} matches + * the given predicate + */ + public static T find( + Iterator iterator, Predicate predicate) { + return filter(iterator, predicate).next(); + } + + /** + * Returns the first element in {@code iterator} that satisfies the given + * predicate. If no such element is found, {@code defaultValue} will be + * returned from this method and the iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. Note that this can + * usually be handled more naturally using {@code + * tryFind(iterator, predicate).or(defaultValue)}. + * + * @since 7.0 + */ + public static T find(Iterator iterator, Predicate predicate, + @Nullable T defaultValue) { + UnmodifiableIterator filteredIterator = filter(iterator, predicate); + return filteredIterator.hasNext() ? filteredIterator.next() : defaultValue; + } + + /** + * Returns an {@link Optional} containing the first element in {@code + * iterator} that satisfies the given predicate, if such an element exists. If + * no such element is found, an empty {@link Optional} will be returned from + * this method and the the iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. + * + *

Warning: avoid using a {@code predicate} that matches {@code + * null}. If {@code null} is matched in {@code iterator}, a + * NullPointerException will be thrown. + * + * @since 11.0 + */ + public static Optional tryFind( + Iterator iterator, Predicate predicate) { + UnmodifiableIterator filteredIterator = filter(iterator, predicate); + return filteredIterator.hasNext() + ? Optional.of(filteredIterator.next()) + : Optional.absent(); + } + + /** + * Returns the index in {@code iterator} of the first element that satisfies + * the provided {@code predicate}, or {@code -1} if the Iterator has no such + * elements. + * + *

More formally, returns the lowest index {@code i} such that + * {@code predicate.apply(Iterators.get(iterator, i))} returns {@code true}, + * or {@code -1} if there is no such index. + * + *

If -1 is returned, the iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. Otherwise, + * the iterator will be set to the element which satisfies the + * {@code predicate}. + * + * @since 2.0 + */ + public static int indexOf( + Iterator iterator, Predicate predicate) { + checkNotNull(predicate, "predicate"); + int i = 0; + while (iterator.hasNext()) { + T current = iterator.next(); + if (predicate.apply(current)) { + return i; + } + i++; + } + return -1; + } + + /** + * Returns an iterator that applies {@code function} to each element of {@code + * fromIterator}. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After a successful {@code remove()} call, {@code fromIterator} no + * longer contains the corresponding element. + */ + public static Iterator transform(final Iterator fromIterator, + final Function function) { + checkNotNull(function); + return new TransformedIterator(fromIterator) { + @Override + T transform(F from) { + return function.apply(from); + } + }; + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the + * element at the {@code position}th position. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterator} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the number of elements remaining in + * {@code iterator} + */ + public static T get(Iterator iterator, int position) { + checkNonnegative(position); + + int skipped = 0; + while (iterator.hasNext()) { + T t = iterator.next(); + if (skipped++ == position) { + return t; + } + } + + throw new IndexOutOfBoundsException("position (" + position + + ") must be less than the number of elements that remained (" + + skipped + ")"); + } + + private static void checkNonnegative(int position) { + if (position < 0) { + throw new IndexOutOfBoundsException("position (" + position + + ") must not be negative"); + } + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the + * element at the {@code position}th position or {@code defaultValue} + * otherwise. + * + * @param position position of the element to return + * @param defaultValue the default value to return if the iterator is empty + * or if {@code position} is greater than the number of elements + * remaining in {@code iterator} + * @return the element at the specified position in {@code iterator} or + * {@code defaultValue} if {@code iterator} produces fewer than + * {@code position + 1} elements. + * @throws IndexOutOfBoundsException if {@code position} is negative + * @since 4.0 + */ + public static T get(Iterator iterator, int position, @Nullable T defaultValue) { + checkNonnegative(position); + + try { + return get(iterator, position); + } catch (IndexOutOfBoundsException e) { + return defaultValue; + } + } + + /** + * Returns the next element in {@code iterator} or {@code defaultValue} if + * the iterator is empty. The {@link Iterables} analog to this method is + * {@link Iterables#getFirst}. + * + * @param defaultValue the default value to return if the iterator is empty + * @return the next element of {@code iterator} or the default value + * @since 7.0 + */ + public static T getNext(Iterator iterator, @Nullable T defaultValue) { + return iterator.hasNext() ? iterator.next() : defaultValue; + } + + /** + * Advances {@code iterator} to the end, returning the last element. + * + * @return the last element of {@code iterator} + * @throws NoSuchElementException if the iterator is empty + */ + public static T getLast(Iterator iterator) { + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } + + /** + * Advances {@code iterator} to the end, returning the last element or + * {@code defaultValue} if the iterator is empty. + * + * @param defaultValue the default value to return if the iterator is empty + * @return the last element of {@code iterator} + * @since 3.0 + */ + public static T getLast(Iterator iterator, @Nullable T defaultValue) { + return iterator.hasNext() ? getLast(iterator) : defaultValue; + } + + /** + * Calls {@code next()} on {@code iterator}, either {@code numberToSkip} times + * or until {@code hasNext()} returns {@code false}, whichever comes first. + * + * @return the number of elements skipped + * @since 3.0 + * @deprecated This method has been renamed to {@link #advance(java.util.Iterator, int) advance}. + * This method is scheduled to be deleted in Guava 14.0. + */ + @Beta + @Deprecated + public static int skip(Iterator iterator, int numberToSkip) { + return advance(iterator, numberToSkip); + } + + /** + * Calls {@code next()} on {@code iterator}, either {@code numberToAdvance} times + * or until {@code hasNext()} returns {@code false}, whichever comes first. + * + * @return the number of elements the iterator was advanced + * @since 13.0 (since 3.0 as {@code Iterators.skip}) + */ + public static int advance(Iterator iterator, int numberToAdvance) { + checkNotNull(iterator); + checkArgument(numberToAdvance >= 0, "number to advance cannot be negative"); + + int i; + for (i = 0; i < numberToAdvance && iterator.hasNext(); i++) { + iterator.next(); + } + return i; + } + + /** + * Creates an iterator returning the first {@code limitSize} elements of the + * given iterator. If the original iterator does not contain that many + * elements, the returned iterator will have the same behavior as the original + * iterator. The returned iterator supports {@code remove()} if the original + * iterator does. + * + * @param iterator the iterator to limit + * @param limitSize the maximum number of elements in the returned iterator + * @throws IllegalArgumentException if {@code limitSize} is negative + * @since 3.0 + */ + public static Iterator limit( + final Iterator iterator, final int limitSize) { + checkNotNull(iterator); + checkArgument(limitSize >= 0, "limit is negative"); + return new Iterator() { + private int count; + + @Override + public boolean hasNext() { + return count < limitSize && iterator.hasNext(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + + /** + * Returns a view of the supplied {@code iterator} that removes each element + * from the supplied {@code iterator} as it is returned. + * + *

The provided iterator must support {@link Iterator#remove()} or + * else the returned iterator will fail on the first call to {@code + * next}. + * + * @param iterator the iterator to remove and return elements from + * @return an iterator that removes and returns elements from the + * supplied iterator + * @since 2.0 + */ + public static Iterator consumingIterator(final Iterator iterator) { + checkNotNull(iterator); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + T next = iterator.next(); + iterator.remove(); + return next; + } + }; + } + + // Methods only in Iterators, not in Iterables + + /** + * Clears the iterator using its remove method. + */ + static void clear(Iterator iterator) { + checkNotNull(iterator); + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } + + /** + * Returns an iterator containing the elements of {@code array} in order. The + * returned iterator is a view of the array; subsequent changes to the array + * will be reflected in the iterator. + * + *

Note: It is often preferable to represent your data using a + * collection type, for example using {@link Arrays#asList(Object[])}, making + * this method unnecessary. + * + *

The {@code Iterable} equivalent of this method is either {@link + * Arrays#asList(Object[])}, {@link ImmutableList#copyOf(Object[])}}, + * or {@link ImmutableList#of}. + */ + public static UnmodifiableIterator forArray(final T... array) { + // TODO(kevinb): compare performance with Arrays.asList(array).iterator(). + checkNotNull(array); // eager for GWT. + return new AbstractIndexedListIterator(array.length) { + @Override protected T get(int index) { + return array[index]; + } + }; + } + + /** + * Returns a list iterator containing the elements in the specified range of + * {@code array} in order, starting at the specified index. + * + *

The {@code Iterable} equivalent of this method is {@code + * Arrays.asList(array).subList(offset, offset + length).listIterator(index)}. + */ + static UnmodifiableListIterator forArray( + final T[] array, final int offset, int length, int index) { + checkArgument(length >= 0); + int end = offset + length; + + // Technically we should give a slightly more descriptive error on overflow + Preconditions.checkPositionIndexes(offset, end, array.length); + + /* + * We can't use call the two-arg constructor with arguments (offset, end) + * because the returned Iterator is a ListIterator that may be moved back + * past the beginning of the iteration. + */ + return new AbstractIndexedListIterator(length, index) { + @Override protected T get(int index) { + return array[offset + index]; + } + }; + } + + /** + * Returns an iterator containing only {@code value}. + * + *

The {@link Iterable} equivalent of this method is {@link + * Collections#singleton}. + */ + public static UnmodifiableIterator singletonIterator( + @Nullable final T value) { + return new UnmodifiableIterator() { + boolean done; + @Override + public boolean hasNext() { + return !done; + } + @Override + public T next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; + } + }; + } + + /** + * Adapts an {@code Enumeration} to the {@code Iterator} interface. + * + *

This method has no equivalent in {@link Iterables} because viewing an + * {@code Enumeration} as an {@code Iterable} is impossible. However, the + * contents can be copied into a collection using {@link + * Collections#list}. + */ + public static UnmodifiableIterator forEnumeration( + final Enumeration enumeration) { + checkNotNull(enumeration); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + @Override + public T next() { + return enumeration.nextElement(); + } + }; + } + + /** + * Adapts an {@code Iterator} to the {@code Enumeration} interface. + * + *

The {@code Iterable} equivalent of this method is either {@link + * Collections#enumeration} (if you have a {@link Collection}), or + * {@code Iterators.asEnumeration(collection.iterator())}. + */ + public static Enumeration asEnumeration(final Iterator iterator) { + checkNotNull(iterator); + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + @Override + public T nextElement() { + return iterator.next(); + } + }; + } + + /** + * Implementation of PeekingIterator that avoids peeking unless necessary. + */ + private static class PeekingImpl implements PeekingIterator { + + private final Iterator iterator; + private boolean hasPeeked; + private E peekedElement; + + public PeekingImpl(Iterator iterator) { + this.iterator = checkNotNull(iterator); + } + + @Override + public boolean hasNext() { + return hasPeeked || iterator.hasNext(); + } + + @Override + public E next() { + if (!hasPeeked) { + return iterator.next(); + } + E result = peekedElement; + hasPeeked = false; + peekedElement = null; + return result; + } + + @Override + public void remove() { + checkState(!hasPeeked, "Can't remove after you've peeked at next"); + iterator.remove(); + } + + @Override + public E peek() { + if (!hasPeeked) { + peekedElement = iterator.next(); + hasPeeked = true; + } + return peekedElement; + } + } + + /** + * Returns a {@code PeekingIterator} backed by the given iterator. + * + *

Calls to the {@code peek} method with no intervening calls to {@code + * next} do not affect the iteration, and hence return the same object each + * time. A subsequent call to {@code next} is guaranteed to return the same + * object again. For example:

   {@code
+   *
+   *   PeekingIterator peekingIterator =
+   *       Iterators.peekingIterator(Iterators.forArray("a", "b"));
+   *   String a1 = peekingIterator.peek(); // returns "a"
+   *   String a2 = peekingIterator.peek(); // also returns "a"
+   *   String a3 = peekingIterator.next(); // also returns "a"}
+ * + * Any structural changes to the underlying iteration (aside from those + * performed by the iterator's own {@link PeekingIterator#remove()} method) + * will leave the iterator in an undefined state. + * + *

The returned iterator does not support removal after peeking, as + * explained by {@link PeekingIterator#remove()}. + * + *

Note: If the given iterator is already a {@code PeekingIterator}, + * it might be returned to the caller, although this is neither + * guaranteed to occur nor required to be consistent. For example, this + * method might choose to pass through recognized implementations of + * {@code PeekingIterator} when the behavior of the implementation is + * known to meet the contract guaranteed by this method. + * + *

There is no {@link Iterable} equivalent to this method, so use this + * method to wrap each individual iterator as it is generated. + * + * @param iterator the backing iterator. The {@link PeekingIterator} assumes + * ownership of this iterator, so users should cease making direct calls + * to it after calling this method. + * @return a peeking iterator backed by that iterator. Apart from the + * additional {@link PeekingIterator#peek()} method, this iterator behaves + * exactly the same as {@code iterator}. + */ + public static PeekingIterator peekingIterator( + Iterator iterator) { + if (iterator instanceof PeekingImpl) { + // Safe to cast to because PeekingImpl only uses T + // covariantly (and cannot be subclassed to add non-covariant uses). + @SuppressWarnings("unchecked") + PeekingImpl peeking = (PeekingImpl) iterator; + return peeking; + } + return new PeekingImpl(iterator); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static PeekingIterator peekingIterator( + PeekingIterator iterator) { + return checkNotNull(iterator); + } + + /** + * Returns an iterator over the merged contents of all given + * {@code iterators}, traversing every element of the input iterators. + * Equivalent entries will not be de-duplicated. + * + *

Callers must ensure that the source {@code iterators} are in + * non-descending order as this method does not sort its input. + * + *

For any equivalent elements across all {@code iterators}, it is + * undefined which element is returned first. + * + * @since 11.0 + */ + @Beta + public static UnmodifiableIterator mergeSorted( + Iterable> iterators, + Comparator comparator) { + checkNotNull(iterators, "iterators"); + checkNotNull(comparator, "comparator"); + + return new MergingIterator(iterators, comparator); + } + + /** + * An iterator that performs a lazy N-way merge, calculating the next value + * each time the iterator is polled. This amortizes the sorting cost over the + * iteration and requires less memory than sorting all elements at once. + * + *

Retrieving a single element takes approximately O(log(M)) time, where M + * is the number of iterators. (Retrieving all elements takes approximately + * O(N*log(M)) time, where N is the total number of elements.) + */ + private static class MergingIterator extends AbstractIterator { + final Queue> queue; + final Comparator comparator; + + public MergingIterator(Iterable> iterators, + Comparator itemComparator) { + this.comparator = itemComparator; + + // A comparator that's used by the heap, allowing the heap + // to be sorted based on the top of each iterator. + Comparator> heapComparator = + new Comparator>() { + @Override + public int compare(PeekingIterator o1, PeekingIterator o2) { + return comparator.compare(o1.peek(), o2.peek()); + } + }; + + queue = new PriorityQueue>(2, heapComparator); + + for (Iterator iterator : iterators) { + if (iterator.hasNext()) { + queue.add(Iterators.peekingIterator(iterator)); + } + } + } + + @Override + protected T computeNext() { + if (queue.isEmpty()) { + return endOfData(); + } + + PeekingIterator nextIter = queue.poll(); + T next = nextIter.next(); + + if (nextIter.hasNext()) { + queue.add(nextIter); + } + + return next; + } + } + + /** + * Precondition tester for {@code Iterator.remove()} that throws an exception with a consistent + * error message. + */ + static void checkRemove(boolean canRemove) { + checkState(canRemove, "no calls to next() since the last call to remove()"); + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + static ListIterator cast(Iterator iterator) { + return (ListIterator) iterator; + } +} diff --git a/guava/src/com/google/common/collect/LexicographicalOrdering.java b/guava/src/com/google/common/collect/LexicographicalOrdering.java new file mode 100644 index 0000000..0d993e4 --- /dev/null +++ b/guava/src/com/google/common/collect/LexicographicalOrdering.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Iterator; + +import javax.annotation.Nullable; + +/** + * An ordering which sorts iterables by comparing corresponding elements + * pairwise. + */ +@GwtCompatible(serializable = true) +final class LexicographicalOrdering + extends Ordering> implements Serializable { + final Ordering elementOrder; + + LexicographicalOrdering(Ordering elementOrder) { + this.elementOrder = elementOrder; + } + + @Override public int compare( + Iterable leftIterable, Iterable rightIterable) { + Iterator left = leftIterable.iterator(); + Iterator right = rightIterable.iterator(); + while (left.hasNext()) { + if (!right.hasNext()) { + return LEFT_IS_GREATER; // because it's longer + } + int result = elementOrder.compare(left.next(), right.next()); + if (result != 0) { + return result; + } + } + if (right.hasNext()) { + return RIGHT_IS_GREATER; // because it's longer + } + return 0; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof LexicographicalOrdering) { + LexicographicalOrdering that = (LexicographicalOrdering) object; + return this.elementOrder.equals(that.elementOrder); + } + return false; + } + + @Override public int hashCode() { + return elementOrder.hashCode() ^ 2075626741; // meaningless + } + + @Override public String toString() { + return elementOrder + ".lexicographical()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/LinkedHashMultimap.java b/guava/src/com/google/common/collect/LinkedHashMultimap.java new file mode 100644 index 0000000..b50cc78 --- /dev/null +++ b/guava/src/com/google/common/collect/LinkedHashMultimap.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of {@code Multimap} that does not allow duplicate key-value + * entries and that returns collections whose iterators follow the ordering in + * which the data was added to the multimap. + * + *

The collections returned by {@code keySet}, {@code keys}, and {@code + * asMap} iterate through the keys in the order they were first added to the + * multimap. Similarly, {@code get}, {@code removeAll}, and {@code + * replaceValues} return collections that iterate through the values in the + * order they were added. The collections generated by {@code entries} and + * {@code values} iterate across the key-value mappings in the order they were + * added to the multimap. + * + *

The iteration ordering of the collections generated by {@code keySet}, + * {@code keys}, and {@code asMap} has a few subtleties. As long as the set of + * keys remains unchanged, adding or removing mappings does not affect the key + * iteration order. However, if you remove all values associated with a key and + * then add the key back to the multimap, that key will come last in the key + * iteration order. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public final class LinkedHashMultimap extends AbstractSetMultimap { + + /** + * Creates a new, empty {@code LinkedHashMultimap} with the default initial + * capacities. + */ + public static LinkedHashMultimap create() { + return new LinkedHashMultimap(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); + } + + /** + * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold + * the specified numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static LinkedHashMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new LinkedHashMultimap( + Maps.capacity(expectedKeys), + Maps.capacity(expectedValuesPerKey)); + } + + /** + * Constructs a {@code LinkedHashMultimap} with the same mappings as the + * specified multimap. If a key-value mapping appears multiple times in the + * input multimap, it only appears once in the constructed multimap. The new + * multimap has the same {@link Multimap#entries()} iteration order as the + * input multimap, except for excluding duplicate mappings. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedHashMultimap create( + Multimap multimap) { + LinkedHashMultimap result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); + result.putAll(multimap); + return result; + } + + private interface ValueSetLink { + ValueSetLink getPredecessorInValueSet(); + ValueSetLink getSuccessorInValueSet(); + + void setPredecessorInValueSet(ValueSetLink entry); + void setSuccessorInValueSet(ValueSetLink entry); + } + + private static void succeedsInValueSet(ValueSetLink pred, ValueSetLink succ) { + pred.setSuccessorInValueSet(succ); + succ.setPredecessorInValueSet(pred); + } + + private static void succeedsInMultimap( + ValueEntry pred, ValueEntry succ) { + pred.setSuccessorInMultimap(succ); + succ.setPredecessorInMultimap(pred); + } + + private static void deleteFromValueSet(ValueSetLink entry) { + succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); + } + + private static void deleteFromMultimap(ValueEntry entry) { + succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); + } + + /** + * LinkedHashMultimap entries are in no less than three coexisting linked lists: + * a row in the hash table for a Set associated with a key, the linked list + * of insertion-ordered entries in that Set, and the linked list of entries + * in the LinkedHashMultimap as a whole. + */ + private static final class ValueEntry extends AbstractMapEntry + implements ValueSetLink { + final K key; + final V value; + final int valueHash; + + @Nullable ValueEntry nextInValueSetHashRow; + + ValueSetLink predecessorInValueSet; + ValueSetLink successorInValueSet; + + ValueEntry predecessorInMultimap; + ValueEntry successorInMultimap; + + ValueEntry(@Nullable K key, @Nullable V value, int valueHash, + @Nullable ValueEntry nextInValueSetHashRow) { + this.key = key; + this.value = value; + this.valueHash = valueHash; + this.nextInValueSetHashRow = nextInValueSetHashRow; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return predecessorInValueSet; + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return successorInValueSet; + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + predecessorInValueSet = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + successorInValueSet = entry; + } + + public ValueEntry getPredecessorInMultimap() { + return predecessorInMultimap; + } + + public ValueEntry getSuccessorInMultimap() { + return successorInMultimap; + } + + public void setSuccessorInMultimap(ValueEntry multimapSuccessor) { + this.successorInMultimap = multimapSuccessor; + } + + public void setPredecessorInMultimap(ValueEntry multimapPredecessor) { + this.predecessorInMultimap = multimapPredecessor; + } + } + + private static final int DEFAULT_KEY_CAPACITY = 16; + private static final int DEFAULT_VALUE_SET_CAPACITY = 2; + + private static final int MAX_VALUE_SET_TABLE_SIZE = Ints.MAX_POWER_OF_TWO; + + @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; + private transient ValueEntry multimapHeaderEntry; + + private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { + super(new LinkedHashMap>(keyCapacity)); + + checkArgument(valueSetCapacity >= 0, + "expectedValuesPerKey must be >= 0 but was %s", valueSetCapacity); + + this.valueSetCapacity = valueSetCapacity; + this.multimapHeaderEntry = new ValueEntry(null, null, 0, null); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code LinkedHashSet} for a collection of values for + * one key. + * + * @return a new {@code LinkedHashSet} containing a collection of values for + * one key + */ + @Override + Set createCollection() { + return new LinkedHashSet(valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

Creates a decorated insertion-ordered set that also keeps track of the + * order in which key-value pairs are added to the multimap. + * + * @param key key to associate with values in the collection + * @return a new decorated set containing a collection of values for one key + */ + @Override + Collection createCollection(K key) { + return new ValueSet(key, valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

If {@code values} is not empty and the multimap already contains a + * mapping for {@code key}, the {@code keySet()} ordering is unchanged. + * However, the provided values always come last in the {@link #entries()} and + * {@link #values()} iteration orderings. + */ + @Override + public Set replaceValues(K key, Iterable values) { + return super.replaceValues(key, values); + } + + /** + * Returns a set of all key-value pairs. Changes to the returned set will + * update the underlying multimap, and vice versa. The entries set does not + * support the {@code add} or {@code addAll} operations. + * + *

The iterator generated by the returned set traverses the entries in the + * order they were added to the multimap. + * + *

Each entry is an immutable snapshot of a key-value mapping in the + * multimap, taken at the time the entry is returned by a method call to the + * collection or its iterator. + */ + @Override public Set> entries() { + return super.entries(); + } + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + *

The iterator generated by the returned collection traverses the values + * in the order they were added to the multimap. + */ + @Override public Collection values() { + return super.values(); + } + + @VisibleForTesting + final class ValueSet extends Sets.ImprovedAbstractSet implements ValueSetLink { + /* + * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory + * consumption. + */ + + private final K key; + private ValueEntry[] hashTable; + private int size = 0; + private int modCount = 0; + + // We use the set object itself as the end of the linked list, avoiding an unnecessary + // entry object per key. + private ValueSetLink firstEntry; + private ValueSetLink lastEntry; + + ValueSet(K key, int expectedValues) { + this.key = key; + this.firstEntry = this; + this.lastEntry = this; + // Round expected values up to a power of 2 to get the table size. + int tableSize = Integer.highestOneBit(Math.max(expectedValues, 2) - 1) << 1; + if (tableSize < 0) { + tableSize = MAX_VALUE_SET_TABLE_SIZE; + } + + @SuppressWarnings("unchecked") + ValueEntry[] hashTable = new ValueEntry[tableSize]; + this.hashTable = hashTable; + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return lastEntry; + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return firstEntry; + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + lastEntry = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + firstEntry = entry; + } + + @Override + public Iterator iterator() { + return new Iterator() { + ValueSetLink nextEntry = firstEntry; + ValueEntry toRemove; + int expectedModCount = modCount; + + private void checkForComodification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForComodification(); + return nextEntry != ValueSet.this; + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry entry = (ValueEntry) nextEntry; + V result = entry.getValue(); + toRemove = entry; + nextEntry = entry.getSuccessorInValueSet(); + return result; + } + + @Override + public void remove() { + checkForComodification(); + Iterators.checkRemove(toRemove != null); + Object o = toRemove.getValue(); + int hash = (o == null) ? 0 : o.hashCode(); + int row = Hashing.smear(hash) & (hashTable.length - 1); + ValueEntry prev = null; + for (ValueEntry entry = hashTable[row]; entry != null; + prev = entry, entry = entry.nextInValueSetHashRow) { + if (entry == toRemove) { + if (prev == null) { + // first entry in row + hashTable[row] = entry.nextInValueSetHashRow; + } else { + prev.nextInValueSetHashRow = entry.nextInValueSetHashRow; + } + deleteFromValueSet(toRemove); + deleteFromMultimap(toRemove); + size--; + expectedModCount = ++modCount; + break; + } + } + toRemove = null; + } + }; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(@Nullable Object o) { + int hash = (o == null) ? 0 : o.hashCode(); + int row = Hashing.smear(hash) & (hashTable.length - 1); + + for (ValueEntry entry = hashTable[row]; entry != null; + entry = entry.nextInValueSetHashRow) { + if (hash == entry.valueHash && Objects.equal(o, entry.getValue())) { + return true; + } + } + return false; + } + + /** + * The threshold above which the hash table should be rebuilt. + */ + @VisibleForTesting int threshold() { + return hashTable.length; // load factor of 1.0 + } + + @Override + public boolean add(@Nullable V value) { + int hash = (value == null) ? 0 : value.hashCode(); + int row = Hashing.smear(hash) & (hashTable.length - 1); + + ValueEntry rowHead = hashTable[row]; + for (ValueEntry entry = rowHead; entry != null; + entry = entry.nextInValueSetHashRow) { + if (hash == entry.valueHash && Objects.equal(value, entry.getValue())) { + return false; + } + } + + ValueEntry newEntry = new ValueEntry(key, value, hash, rowHead); + succeedsInValueSet(lastEntry, newEntry); + succeedsInValueSet(newEntry, this); + succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); + succeedsInMultimap(newEntry, multimapHeaderEntry); + hashTable[row] = newEntry; + size++; + modCount++; + rehashIfNecessary(); + return true; + } + + private void rehashIfNecessary() { + if (size > threshold() && hashTable.length < MAX_VALUE_SET_TABLE_SIZE) { + @SuppressWarnings("unchecked") + ValueEntry[] hashTable = new ValueEntry[this.hashTable.length * 2]; + this.hashTable = hashTable; + int mask = hashTable.length - 1; + for (ValueSetLink entry = firstEntry; + entry != this; entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + int row = Hashing.smear(valueEntry.valueHash) & mask; + valueEntry.nextInValueSetHashRow = hashTable[row]; + hashTable[row] = valueEntry; + } + } + } + + @Override + public boolean remove(@Nullable Object o) { + int hash = (o == null) ? 0 : o.hashCode(); + int row = Hashing.smear(hash) & (hashTable.length - 1); + + ValueEntry prev = null; + for (ValueEntry entry = hashTable[row]; entry != null; + prev = entry, entry = entry.nextInValueSetHashRow) { + if (hash == entry.valueHash && Objects.equal(o, entry.getValue())) { + if (prev == null) { + // first entry in the row + hashTable[row] = entry.nextInValueSetHashRow; + } else { + prev.nextInValueSetHashRow = entry.nextInValueSetHashRow; + } + deleteFromValueSet(entry); + deleteFromMultimap(entry); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public void clear() { + Arrays.fill(hashTable, null); + size = 0; + for (ValueSetLink entry = firstEntry; + entry != this; entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + deleteFromMultimap(valueEntry); + } + succeedsInValueSet(this, this); + modCount++; + } + } + + @Override + Iterator> createEntryIterator() { + return new Iterator>() { + ValueEntry nextEntry = multimapHeaderEntry.successorInMultimap; + ValueEntry toRemove; + + @Override + public boolean hasNext() { + return nextEntry != multimapHeaderEntry; + } + + @Override + public Map.Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry result = nextEntry; + toRemove = result; + nextEntry = nextEntry.successorInMultimap; + return result; + } + + @Override + public void remove() { + Iterators.checkRemove(toRemove != null); + LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); + toRemove = null; + } + }; + } + + @Override + public void clear() { + super.clear(); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } + + /** + * @serialData the expected values per key, the number of distinct keys, + * the number of entries, and the entries in order + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(valueSetCapacity); + stream.writeInt(keySet().size()); + for (K key : keySet()) { + stream.writeObject(key); + } + stream.writeInt(size()); + for (Map.Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + multimapHeaderEntry = new ValueEntry(null, null, 0, null); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + valueSetCapacity = stream.readInt(); + int distinctKeys = stream.readInt(); + Map> map = + new LinkedHashMap>(Maps.capacity(distinctKeys)); + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") + K key = (K) stream.readObject(); + map.put(key, createCollection(key)); + } + int entries = stream.readInt(); + for (int i = 0; i < entries; i++) { + @SuppressWarnings("unchecked") + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") + V value = (V) stream.readObject(); + map.get(key).add(value); + } + setMap(map); + } + + @GwtIncompatible("java serialization not supported") + private static final long serialVersionUID = 1; +} diff --git a/guava/src/com/google/common/collect/LinkedHashMultiset.java b/guava/src/com/google/common/collect/LinkedHashMultiset.java new file mode 100644 index 0000000..9b8c45b --- /dev/null +++ b/guava/src/com/google/common/collect/LinkedHashMultiset.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.LinkedHashMap; + +/** + * A {@code Multiset} implementation with predictable iteration order. Its + * iterator orders elements according to when the first occurrence of the + * element was added. When the multiset contains multiple instances of an + * element, those instances are consecutive in the iteration order. If all + * occurrences of an element are removed, after which that element is added to + * the multiset, the element will appear at the end of the iteration. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public final class LinkedHashMultiset extends AbstractMapBasedMultiset { + + /** + * Creates a new, empty {@code LinkedHashMultiset} using the default initial + * capacity. + */ + public static LinkedHashMultiset create() { + return new LinkedHashMultiset(); + } + + /** + * Creates a new, empty {@code LinkedHashMultiset} with the specified expected + * number of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static LinkedHashMultiset create(int distinctElements) { + return new LinkedHashMultiset(distinctElements); + } + + /** + * Creates a new {@code LinkedHashMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself + * a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static LinkedHashMultiset create( + Iterable elements) { + LinkedHashMultiset multiset = + create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private LinkedHashMultiset() { + super(new LinkedHashMap()); + } + + private LinkedHashMultiset(int distinctElements) { + // Could use newLinkedHashMapWithExpectedSize() if it existed + super(new LinkedHashMap(Maps.capacity(distinctElements))); + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap(new LinkedHashMap( + Maps.capacity(distinctElements))); + Serialization.populateMultiset(this, stream, distinctElements); + } + + @GwtIncompatible("not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/LinkedListMultimap.java b/guava/src/com/google/common/collect/LinkedListMultimap.java new file mode 100644 index 0000000..79127b7 --- /dev/null +++ b/guava/src/com/google/common/collect/LinkedListMultimap.java @@ -0,0 +1,995 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.util.Collections.unmodifiableList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSequentialList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An implementation of {@code ListMultimap} that supports deterministic + * iteration order for both keys and values. The iteration order is preserved + * across non-distinct key values. For example, for the following multimap + * definition:

   {@code
+ *
+ *   Multimap multimap = LinkedListMultimap.create();
+ *   multimap.put(key1, foo);
+ *   multimap.put(key2, bar);
+ *   multimap.put(key1, baz);}
+ * + * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, + * and similarly for {@link #entries()}. Unlike {@link LinkedHashMultimap}, the + * iteration order is kept consistent between keys, entries and values. For + * example, calling:
   {@code
+ *
+ *   map.remove(key1, foo);}
+ * + * changes the entries iteration order to {@code [key2=bar, key1=baz]} and the + * key iteration order to {@code [key2, key1]}. The {@link #entries()} iterator + * returns mutable map entries, and {@link #replaceValues} attempts to preserve + * iteration order as much as possible. + * + *

The collections returned by {@link #keySet()} and {@link #asMap} iterate + * through the keys in the order they were first added to the multimap. + * Similarly, {@link #get}, {@link #removeAll}, and {@link #replaceValues} + * return collections that iterate through the values in the order they were + * added. The collections generated by {@link #entries()}, {@link #keys()}, and + * {@link #values} iterate across the key-value mappings in the order they were + * added to the multimap. + * + *

The {@link #values()} and {@link #entries()} methods both return a + * {@code List}, instead of the {@code Collection} specified by the {@link + * ListMultimap} interface. + * + *

The methods {@link #get}, {@link #keySet()}, {@link #keys()}, + * {@link #values}, {@link #entries()}, and {@link #asMap} return collections + * that are views of the multimap. If the multimap is modified while an + * iteration over any of those collections is in progress, except through the + * iterator's methods, the results of the iteration are undefined. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedListMultimap}. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Mike Bostock + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public class LinkedListMultimap + implements ListMultimap, Serializable { + /* + * Order is maintained using a linked list containing all key-value pairs. In + * addition, a series of disjoint linked lists of "siblings", each containing + * the values for a specific key, is used to implement {@link + * ValueForKeyIterator} in constant time. + */ + + private static final class Node { + final K key; + V value; + Node next; // the next node (with any key) + Node previous; // the previous node (with any key) + Node nextSibling; // the next node with the same key + Node previousSibling; // the previous node with the same key + + Node(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Override public String toString() { + return key + "=" + value; + } + } + + private transient Node head; // the head for all keys + private transient Node tail; // the tail for all keys + private transient Multiset keyCount; // the number of values for each key + private transient Map> keyToKeyHead; // the head for a given key + private transient Map> keyToKeyTail; // the tail for a given key + + /** + * Creates a new, empty {@code LinkedListMultimap} with the default initial + * capacity. + */ + public static LinkedListMultimap create() { + return new LinkedListMultimap(); + } + + /** + * Constructs an empty {@code LinkedListMultimap} with enough capacity to hold + * the specified number of keys without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @throws IllegalArgumentException if {@code expectedKeys} is negative + */ + public static LinkedListMultimap create(int expectedKeys) { + return new LinkedListMultimap(expectedKeys); + } + + /** + * Constructs a {@code LinkedListMultimap} with the same mappings as the + * specified {@code Multimap}. The new multimap has the same + * {@link Multimap#entries()} iteration order as the input multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedListMultimap create( + Multimap multimap) { + return new LinkedListMultimap(multimap); + } + + LinkedListMultimap() { + keyCount = LinkedHashMultiset.create(); + keyToKeyHead = Maps.newHashMap(); + keyToKeyTail = Maps.newHashMap(); + } + + private LinkedListMultimap(int expectedKeys) { + keyCount = LinkedHashMultiset.create(expectedKeys); + keyToKeyHead = Maps.newHashMapWithExpectedSize(expectedKeys); + keyToKeyTail = Maps.newHashMapWithExpectedSize(expectedKeys); + } + + private LinkedListMultimap(Multimap multimap) { + this(multimap.keySet().size()); + putAll(multimap); + } + + /** + * Adds a new node for the specified key-value pair before the specified + * {@code nextSibling} element, or at the end of the list if {@code + * nextSibling} is null. Note: if {@code nextSibling} is specified, it MUST be + * for an node for the same {@code key}! + */ + private Node addNode( + @Nullable K key, @Nullable V value, @Nullable Node nextSibling) { + Node node = new Node(key, value); + if (head == null) { // empty list + head = tail = node; + keyToKeyHead.put(key, node); + keyToKeyTail.put(key, node); + } else if (nextSibling == null) { // non-empty list, add to tail + tail.next = node; + node.previous = tail; + Node keyTail = keyToKeyTail.get(key); + if (keyTail == null) { // first for this key + keyToKeyHead.put(key, node); + } else { + keyTail.nextSibling = node; + node.previousSibling = keyTail; + } + keyToKeyTail.put(key, node); + tail = node; + } else { // non-empty list, insert before nextSibling + node.previous = nextSibling.previous; + node.previousSibling = nextSibling.previousSibling; + node.next = nextSibling; + node.nextSibling = nextSibling; + if (nextSibling.previousSibling == null) { // nextSibling was key head + keyToKeyHead.put(key, node); + } else { + nextSibling.previousSibling.nextSibling = node; + } + if (nextSibling.previous == null) { // nextSibling was head + head = node; + } else { + nextSibling.previous.next = node; + } + nextSibling.previous = node; + nextSibling.previousSibling = node; + } + keyCount.add(key); + return node; + } + + /** + * Removes the specified node from the linked list. This method is only + * intended to be used from the {@code Iterator} classes. See also {@link + * LinkedListMultimap#removeAllNodes(Object)}. + */ + private void removeNode(Node node) { + if (node.previous != null) { + node.previous.next = node.next; + } else { // node was head + head = node.next; + } + if (node.next != null) { + node.next.previous = node.previous; + } else { // node was tail + tail = node.previous; + } + if (node.previousSibling != null) { + node.previousSibling.nextSibling = node.nextSibling; + } else if (node.nextSibling != null) { // node was key head + keyToKeyHead.put(node.key, node.nextSibling); + } else { + keyToKeyHead.remove(node.key); // don't leak a key-null entry + } + if (node.nextSibling != null) { + node.nextSibling.previousSibling = node.previousSibling; + } else if (node.previousSibling != null) { // node was key tail + keyToKeyTail.put(node.key, node.previousSibling); + } else { + keyToKeyTail.remove(node.key); // don't leak a key-null entry + } + keyCount.remove(node.key); + } + + /** Removes all nodes for the specified key. */ + private void removeAllNodes(@Nullable Object key) { + for (Iterator i = new ValueForKeyIterator(key); i.hasNext();) { + i.next(); + i.remove(); + } + } + + /** Helper method for verifying that an iterator element is present. */ + private static void checkElement(@Nullable Object node) { + if (node == null) { + throw new NoSuchElementException(); + } + } + + /** An {@code Iterator} over all nodes. */ + private class NodeIterator implements ListIterator> { + int nextIndex; + Node next; + Node current; + Node previous; + + NodeIterator() { + next = head; + } + NodeIterator(int index) { + int size = size(); + Preconditions.checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = tail; + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = head; + while (index-- > 0) { + next(); + } + } + current = null; + } + @Override + public boolean hasNext() { + return next != null; + } + @Override + public Node next() { + checkElement(next); + previous = current = next; + next = next.next; + nextIndex++; + return current; + } + @Override + public void remove() { + checkState(current != null); + if (current != next) { // after call to next() + previous = current.previous; + nextIndex--; + } else { // after call to previous() + next = current.next; + } + removeNode(current); + current = null; + } + @Override + public boolean hasPrevious() { + return previous != null; + } + @Override + public Node previous() { + checkElement(previous); + next = current = previous; + previous = previous.previous; + nextIndex--; + return current; + } + @Override + public int nextIndex() { + return nextIndex; + } + @Override + public int previousIndex() { + return nextIndex - 1; + } + @Override + public void set(Node e) { + throw new UnsupportedOperationException(); + } + @Override + public void add(Node e) { + throw new UnsupportedOperationException(); + } + void setValue(V value) { + checkState(current != null); + current.value = value; + } + } + + /** An {@code Iterator} over distinct keys in key head order. */ + private class DistinctKeyIterator implements Iterator { + final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size()); + Node next = head; + Node current; + + @Override + public boolean hasNext() { + return next != null; + } + @Override + public K next() { + checkElement(next); + current = next; + seenKeys.add(current.key); + do { // skip ahead to next unseen key + next = next.next; + } while ((next != null) && !seenKeys.add(next.key)); + return current.key; + } + @Override + public void remove() { + checkState(current != null); + removeAllNodes(current.key); + current = null; + } + } + + /** A {@code ListIterator} over values for a specified key. */ + private class ValueForKeyIterator implements ListIterator { + final Object key; + int nextIndex; + Node next; + Node current; + Node previous; + + /** Constructs a new iterator over all values for the specified key. */ + ValueForKeyIterator(@Nullable Object key) { + this.key = key; + next = keyToKeyHead.get(key); + } + + /** + * Constructs a new iterator over all values for the specified key starting + * at the specified index. This constructor is optimized so that it starts + * at either the head or the tail, depending on which is closer to the + * specified index. This allows adds to the tail to be done in constant + * time. + * + * @throws IndexOutOfBoundsException if index is invalid + */ + public ValueForKeyIterator(@Nullable Object key, int index) { + int size = keyCount.count(key); + Preconditions.checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = keyToKeyTail.get(key); + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = keyToKeyHead.get(key); + while (index-- > 0) { + next(); + } + } + this.key = key; + current = null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public V next() { + checkElement(next); + previous = current = next; + next = next.nextSibling; + nextIndex++; + return current.value; + } + + @Override + public boolean hasPrevious() { + return previous != null; + } + + @Override + public V previous() { + checkElement(previous); + next = current = previous; + previous = previous.previousSibling; + nextIndex--; + return current.value; + } + + @Override + public int nextIndex() { + return nextIndex; + } + + @Override + public int previousIndex() { + return nextIndex - 1; + } + + @Override + public void remove() { + checkState(current != null); + if (current != next) { // after call to next() + previous = current.previousSibling; + nextIndex--; + } else { // after call to previous() + next = current.nextSibling; + } + removeNode(current); + current = null; + } + + @Override + public void set(V value) { + checkState(current != null); + current.value = value; + } + + @Override + @SuppressWarnings("unchecked") + public void add(V value) { + previous = addNode((K) key, value, next); + nextIndex++; + current = null; + } + } + + // Query Operations + + @Override + public int size() { + return keyCount.size(); + } + + @Override + public boolean isEmpty() { + return head == null; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return keyToKeyHead.containsKey(key); + } + + @Override + public boolean containsValue(@Nullable Object value) { + for (Iterator> i = new NodeIterator(); i.hasNext();) { + if (Objects.equal(i.next().value, value)) { + return true; + } + } + return false; + } + + @Override + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + for (Iterator i = new ValueForKeyIterator(key); i.hasNext();) { + if (Objects.equal(i.next(), value)) { + return true; + } + } + return false; + } + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + @Override + public boolean put(@Nullable K key, @Nullable V value) { + addNode(key, value, null); + return true; + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + Iterator values = new ValueForKeyIterator(key); + while (values.hasNext()) { + if (Objects.equal(values.next(), value)) { + values.remove(); + return true; + } + } + return false; + } + + // Bulk Operations + + @Override + public boolean putAll(@Nullable K key, Iterable values) { + boolean changed = false; + for (V value : values) { + changed |= put(key, value); + } + return changed; + } + + @Override + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * {@inheritDoc} + * + *

If any entries for the specified {@code key} already exist in the + * multimap, their values are changed in-place without affecting the iteration + * order. + * + *

The returned list is immutable and implements + * {@link java.util.RandomAccess}. + */ + @Override + public List replaceValues(@Nullable K key, Iterable values) { + List oldValues = getCopy(key); + ListIterator keyValues = new ValueForKeyIterator(key); + Iterator newValues = values.iterator(); + + // Replace existing values, if any. + while (keyValues.hasNext() && newValues.hasNext()) { + keyValues.next(); + keyValues.set(newValues.next()); + } + + // Remove remaining old values, if any. + while (keyValues.hasNext()) { + keyValues.next(); + keyValues.remove(); + } + + // Add remaining new values, if any. + while (newValues.hasNext()) { + keyValues.add(newValues.next()); + } + + return oldValues; + } + + private List getCopy(@Nullable Object key) { + return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key))); + } + + /** + * {@inheritDoc} + * + *

The returned list is immutable and implements + * {@link java.util.RandomAccess}. + */ + @Override + public List removeAll(@Nullable Object key) { + List oldValues = getCopy(key); + removeAllNodes(key); + return oldValues; + } + + @Override + public void clear() { + head = null; + tail = null; + keyCount.clear(); + keyToKeyHead.clear(); + keyToKeyTail.clear(); + } + + // Views + + /** + * {@inheritDoc} + * + *

If the multimap is modified while an iteration over the list is in + * progress (except through the iterator's own {@code add}, {@code set} or + * {@code remove} operations) the results of the iteration are undefined. + * + *

The returned list is not serializable and does not have random access. + */ + @Override + public List get(final @Nullable K key) { + return new AbstractSequentialList() { + @Override public int size() { + return keyCount.count(key); + } + @Override public ListIterator listIterator(int index) { + return new ValueForKeyIterator(key, index); + } + @Override public boolean removeAll(Collection c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection c) { + return Iterators.retainAll(iterator(), c); + } + }; + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = new Sets.ImprovedAbstractSet() { + @Override public int size() { + return keyCount.elementSet().size(); + } + @Override public Iterator iterator() { + return new DistinctKeyIterator(); + } + @Override public boolean contains(Object key) { // for performance + return containsKey(key); + } + @Override + public boolean remove(Object o) { // for performance + return !LinkedListMultimap.this.removeAll(o).isEmpty(); + } + }; + } + return result; + } + + private transient Multiset keys; + + @Override + public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = new MultisetView(); + } + return result; + } + + private class MultisetView extends AbstractMultiset { + @Override + public int size() { + return keyCount.size(); + } + + @Override + public int count(Object element) { + return keyCount.count(element); + } + + @Override + Iterator> entryIterator() { + return new TransformedIterator>(new DistinctKeyIterator()) { + @Override + Entry transform(final K key) { + return new Multisets.AbstractEntry() { + @Override + public K getElement() { + return key; + } + + @Override + public int getCount() { + return keyCount.count(key); + } + }; + } + }; + } + + @Override + int distinctElements() { + return elementSet().size(); + } + + @Override public Iterator iterator() { + return new TransformedIterator, K>(new NodeIterator()) { + @Override + K transform(Node node) { + return node.key; + } + }; + } + + @Override + public int remove(@Nullable Object key, int occurrences) { + checkArgument(occurrences >= 0); + int oldCount = count(key); + Iterator values = new ValueForKeyIterator(key); + while ((occurrences-- > 0) && values.hasNext()) { + values.next(); + values.remove(); + } + return oldCount; + } + + @Override + public Set elementSet() { + return keySet(); + } + + @Override public boolean equals(@Nullable Object object) { + return keyCount.equals(object); + } + + @Override public int hashCode() { + return keyCount.hashCode(); + } + + @Override public String toString() { + return keyCount.toString(); // XXX observe order? + } + } + + private transient List valuesList; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * in the order they were added to the multimap. Because the values may have + * duplicates and follow the insertion ordering, this method returns a {@link + * List}, instead of the {@link Collection} specified in the {@link + * ListMultimap} interface. + */ + @Override + public List values() { + List result = valuesList; + if (result == null) { + valuesList = result = new AbstractSequentialList() { + @Override public int size() { + return keyCount.size(); + } + @Override + public ListIterator listIterator(int index) { + final NodeIterator nodes = new NodeIterator(index); + return new TransformedListIterator, V>(nodes) { + @Override + V transform(Node node) { + return node.value; + } + + @Override + public void set(V value) { + nodes.setValue(value); + } + }; + } + }; + } + return result; + } + + private static Entry createEntry(final Node node) { + return new AbstractMapEntry() { + @Override public K getKey() { + return node.key; + } + @Override public V getValue() { + return node.value; + } + @Override public V setValue(V value) { + V oldValue = node.value; + node.value = value; + return oldValue; + } + }; + } + + private transient List> entries; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the entries + * in the order they were added to the multimap. Because the entries may have + * duplicates and follow the insertion ordering, this method returns a {@link + * List}, instead of the {@link Collection} specified in the {@link + * ListMultimap} interface. + * + *

An entry's {@link Entry#getKey} method always returns the same key, + * regardless of what happens subsequently. As long as the corresponding + * key-value mapping is not removed from the multimap, {@link Entry#getValue} + * returns the value from the multimap, which may change over time, and {@link + * Entry#setValue} modifies that value. Removing the mapping from the + * multimap does not alter the value returned by {@code getValue()}, though a + * subsequent {@code setValue()} call won't update the multimap but will lead + * to a revised value being returned by {@code getValue()}. + */ + @Override + public List> entries() { + List> result = entries; + if (result == null) { + entries = result = new AbstractSequentialList>() { + @Override public int size() { + return keyCount.size(); + } + + @Override public ListIterator> listIterator(int index) { + return new TransformedListIterator, Entry>(new NodeIterator(index)) { + @Override + Entry transform(Node node) { + return createEntry(node); + } + }; + } + }; + } + return result; + } + + private transient Map> map; + + @Override + public Map> asMap() { + Map> result = map; + if (result == null) { + map = result = new Multimaps.AsMap() { + @Override + public int size() { + return keyCount.elementSet().size(); + } + + @Override + Multimap multimap() { + return LinkedListMultimap.this; + } + + @Override + Iterator>> entryIterator() { + return new TransformedIterator>>(new DistinctKeyIterator()) { + @Override + Entry> transform(final K key) { + return new AbstractMapEntry>() { + @Override public K getKey() { + return key; + } + + @Override public Collection getValue() { + return LinkedListMultimap.this.get(key); + } + }; + } + }; + } + }; + } + + return result; + } + + // Comparison and hashing + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + @Override public boolean equals(@Nullable Object other) { + if (other == this) { + return true; + } + if (other instanceof Multimap) { + Multimap that = (Multimap) other; + return this.asMap().equals(that.asMap()); + } + return false; + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + @Override public int hashCode() { + return asMap().hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling + * {@code toString} on the map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override public String toString() { + return asMap().toString(); + } + + /** + * @serialData the number of distinct keys, and then for each distinct key: + * the first key, the number of values for that key, and the key's values, + * followed by successive keys and values from the entries() ordering + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size()); + for (Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyCount = LinkedHashMultiset.create(); + keyToKeyHead = Maps.newHashMap(); + keyToKeyTail = Maps.newHashMap(); + int size = stream.readInt(); + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + put(key, value); + } + } + + @GwtIncompatible("java serialization not supported") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ListMultimap.java b/guava/src/com/google/common/collect/ListMultimap.java new file mode 100644 index 0000000..a83b81a --- /dev/null +++ b/guava/src/com/google/common/collect/ListMultimap.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@code Multimap} that can hold duplicate key-value pairs and that maintains + * the insertion ordering of values for a given key. See the {@link Multimap} + * documentation for information common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link List} of values. Though the method signature doesn't say + * so explicitly, the map returned by {@link #asMap} has {@code List} values. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface ListMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + @Override + List get(@Nullable K key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + @Override + List removeAll(@Nullable Object key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + @Override + List replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link List} values. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + * + *

An empty {@code ListMultimap} is equal to any other empty {@code + * Multimap}, including an empty {@code SetMultimap}. + */ + @Override + boolean equals(@Nullable Object obj); +} diff --git a/guava/src/com/google/common/collect/Lists.java b/guava/src/com/google/common/collect/Lists.java new file mode 100644 index 0000000..4f400fb --- /dev/null +++ b/guava/src/com/google/common/collect/Lists.java @@ -0,0 +1,1073 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.AbstractSequentialList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link List} instances. Also see this + * class's counterparts {@link Sets} and {@link Maps}. + * + *

See the Guava User Guide article on + * {@code Lists}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Lists { + private Lists() {} + + // ArrayList + + /** + * Creates a mutable, empty {@code ArrayList} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableList#of()} instead. + * + * @return a new, empty {@code ArrayList} + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList() { + return new ArrayList(); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use an overload of {@link ImmutableList#of()} (for varargs) or + * {@link ImmutableList#copyOf(Object[])} (for an array) instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(E... elements) { + checkNotNull(elements); // for GWT + // Avoid integer overflow when a large array is passed in + int capacity = computeArrayListCapacity(elements.length); + ArrayList list = new ArrayList(capacity); + Collections.addAll(list, elements); + return list; + } + + @VisibleForTesting static int computeArrayListCapacity(int arraySize) { + checkArgument(arraySize >= 0); + + // TODO(kevinb): Figure out the right behavior, and document it + return Ints.saturatedCast(5L + arraySize + (arraySize / 10)); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableList#copyOf(Iterator)} instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterable elements) { + checkNotNull(elements); // for GWT + // Let ArrayList's sizing logic work, if possible + return (elements instanceof Collection) + ? new ArrayList(Collections2.cast(elements)) + : newArrayList(elements.iterator()); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableList#copyOf(Iterator)} instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterator elements) { + checkNotNull(elements); // for GWT + ArrayList list = newArrayList(); + while (elements.hasNext()) { + list.add(elements.next()); + } + return list; + } + + /** + * Creates an {@code ArrayList} instance backed by an array of the + * exact size specified; equivalent to + * {@link ArrayList#ArrayList(int)}. + * + *

Note: if you know the exact size your list will be, consider + * using a fixed-size list ({@link Arrays#asList(Object[])}) or an {@link + * ImmutableList} instead of a growable {@link ArrayList}. + * + *

Note: If you have only an estimate of the eventual size of + * the list, consider padding this estimate by a suitable amount, or simply + * use {@link #newArrayListWithExpectedSize(int)} instead. + * + * @param initialArraySize the exact size of the initial backing array for + * the returned array list ({@code ArrayList} documentation calls this + * value the "capacity") + * @return a new, empty {@code ArrayList} which is guaranteed not to resize + * itself unless its size reaches {@code initialArraySize + 1} + * @throws IllegalArgumentException if {@code initialArraySize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithCapacity( + int initialArraySize) { + checkArgument(initialArraySize >= 0); // for GWT. + return new ArrayList(initialArraySize); + } + + /** + * Creates an {@code ArrayList} instance sized appropriately to hold an + * estimated number of elements without resizing. A small amount of + * padding is added in case the estimate is low. + * + *

Note: If you know the exact number of elements the list + * will hold, or prefer to calculate your own amount of padding, refer to + * {@link #newArrayListWithCapacity(int)}. + * + * @param estimatedSize an estimate of the eventual {@link List#size()} of + * the new list + * @return a new, empty {@code ArrayList}, sized appropriately to hold the + * estimated number of elements + * @throws IllegalArgumentException if {@code estimatedSize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithExpectedSize( + int estimatedSize) { + return new ArrayList(computeArrayListCapacity(estimatedSize)); + } + + // LinkedList + + /** + * Creates an empty {@code LinkedList} instance. + * + *

Note: if you need an immutable empty {@link List}, use + * {@link ImmutableList#of()} instead. + * + * @return a new, empty {@code LinkedList} + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList() { + return new LinkedList(); + } + + /** + * Creates a {@code LinkedList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code LinkedList} containing those elements + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList( + Iterable elements) { + LinkedList list = newLinkedList(); + for (E element : elements) { + list.add(element); + } + return list; + } + + /** + * Creates an empty {@code CopyOnWriteArrayList} instance. + * + *

Note: if you need an immutable empty {@link List}, use + * {@link Collections#emptyList} instead. + * + * @return a new, empty {@code CopyOnWriteArrayList} + * @since 12.0 + */ + @GwtIncompatible("CopyOnWriteArrayList") + public static CopyOnWriteArrayList newCopyOnWriteArrayList() { + return new CopyOnWriteArrayList(); + } + + /** + * Creates a {@code CopyOnWriteArrayList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code CopyOnWriteArrayList} containing those elements + * @since 12.0 + */ + @GwtIncompatible("CopyOnWriteArrayList") + public static CopyOnWriteArrayList newCopyOnWriteArrayList( + Iterable elements) { + // We copy elements to an ArrayList first, rather than incurring the + // quadratic cost of adding them to the COWAL directly. + Collection elementsCollection = (elements instanceof Collection) + ? Collections2.cast(elements) + : newArrayList(elements); + return new CopyOnWriteArrayList(elementsCollection); + } + + /** + * Returns an unmodifiable list containing the specified first element and + * backed by the specified array of additional elements. Changes to the {@code + * rest} array will be reflected in the returned list. Unlike {@link + * Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo... moreFoos)}, in order to avoid overload + * ambiguity or to enforce a minimum argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList(@Nullable E first, E[] rest) { + return new OnePlusArrayList(first, rest); + } + + /** @see Lists#asList(Object, Object[]) */ + private static class OnePlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E[] rest; + + OnePlusArrayList(@Nullable E first, E[] rest) { + this.first = first; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 1; + } + @Override public E get(int index) { + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return (index == 0) ? first : rest[index - 1]; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable list containing the specified first and second + * element, and backed by the specified array of additional elements. Changes + * to the {@code rest} array will be reflected in the returned list. Unlike + * {@link Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo secondFoo, Foo... moreFoos)}, in order to avoid + * overload ambiguity or to enforce a minimum argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param second the second element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList( + @Nullable E first, @Nullable E second, E[] rest) { + return new TwoPlusArrayList(first, second, rest); + } + + /** @see Lists#asList(Object, Object, Object[]) */ + private static class TwoPlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E second; + final E[] rest; + + TwoPlusArrayList(@Nullable E first, @Nullable E second, E[] rest) { + this.first = first; + this.second = second; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 2; + } + @Override public E get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return rest[index - 2]; + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a list that applies {@code function} to each element of {@code + * fromList}. The returned list is a transformed view of {@code fromList}; + * changes to {@code fromList} will be reflected in the returned list and vice + * versa. + * + *

Since functions are not reversible, the transform is one-way and new + * items cannot be stored in the returned list. The {@code add}, + * {@code addAll} and {@code set} methods are unsupported in the returned + * list. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned list to be a view, but it means that the function will be + * applied many times for bulk operations like {@link List#contains} and + * {@link List#hashCode}. For this to perform well, {@code function} should be + * fast. To avoid lazy evaluation when the returned list doesn't need to be a + * view, copy the returned list into a new list of your choosing. + * + *

If {@code fromList} implements {@link RandomAccess}, so will the + * returned list. The returned list is threadsafe if the supplied list and + * function are. + * + *

If only a {@code Collection} or {@code Iterable} input is available, use + * {@link Collections2#transform} or {@link Iterables#transform}. + * + *

Note: serializing the returned list is implemented by serializing + * {@code fromList}, its contents, and {@code function} -- not by + * serializing the transformed values. This can lead to surprising behavior, + * so serializing the returned list is not recommended. Instead, + * copy the list using {@link ImmutableList#copyOf(Collection)} (for example), + * then serialize the copy. Other methods similar to this do not implement + * serialization at all for this reason. + */ + public static List transform( + List fromList, Function function) { + return (fromList instanceof RandomAccess) + ? new TransformingRandomAccessList(fromList, function) + : new TransformingSequentialList(fromList, function); + } + + /** + * Implementation of a sequential transforming list. + * + * @see Lists#transform + */ + private static class TransformingSequentialList + extends AbstractSequentialList implements Serializable { + final List fromList; + final Function function; + + TransformingSequentialList( + List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + /** + * The default implementation inherited is based on iteration and removal of + * each element which can be overkill. That's why we forward this call + * directly to the backing list. + */ + @Override public void clear() { + fromList.clear(); + } + @Override public int size() { + return fromList.size(); + } + @Override public ListIterator listIterator(final int index) { + final ListIterator delegate = fromList.listIterator(index); + return new ListIterator() { + @Override + public void add(T e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + @Override + public T next() { + return function.apply(delegate.next()); + } + + @Override + public int nextIndex() { + return delegate.nextIndex(); + } + + @Override + public T previous() { + return function.apply(delegate.previous()); + } + + @Override + public int previousIndex() { + return delegate.previousIndex(); + } + + @Override + public void remove() { + delegate.remove(); + } + + @Override + public void set(T e) { + throw new UnsupportedOperationException("not supported"); + } + }; + } + + private static final long serialVersionUID = 0; + } + + /** + * Implementation of a transforming random access list. We try to make as many + * of these methods pass-through to the source list as possible so that the + * performance characteristics of the source list and transformed list are + * similar. + * + * @see Lists#transform + */ + private static class TransformingRandomAccessList + extends AbstractList implements RandomAccess, Serializable { + final List fromList; + final Function function; + + TransformingRandomAccessList( + List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + @Override public void clear() { + fromList.clear(); + } + @Override public T get(int index) { + return function.apply(fromList.get(index)); + } + @Override public boolean isEmpty() { + return fromList.isEmpty(); + } + @Override public T remove(int index) { + return function.apply(fromList.remove(index)); + } + @Override public int size() { + return fromList.size(); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns consecutive {@linkplain List#subList(int, int) sublists} of a list, + * each of the same size (the final list may be smaller). For example, + * partitioning a list containing {@code [a, b, c, d, e]} with a partition + * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer list containing + * two inner lists of three and two elements, all in the original order. + * + *

The outer list is unmodifiable, but reflects the latest state of the + * source list. The inner lists are sublist views of the original list, + * produced on demand using {@link List#subList(int, int)}, and are subject + * to all the usual caveats about modification as explained in that API. + * + * @param list the list to return consecutive sublists of + * @param size the desired size of each sublist (the last may be + * smaller) + * @return a list of consecutive sublists + * @throws IllegalArgumentException if {@code partitionSize} is nonpositive + */ + public static List> partition(List list, int size) { + checkNotNull(list); + checkArgument(size > 0); + return (list instanceof RandomAccess) + ? new RandomAccessPartition(list, size) + : new Partition(list, size); + } + + private static class Partition extends AbstractList> { + final List list; + final int size; + + Partition(List list, int size) { + this.list = list; + this.size = size; + } + + @Override public List get(int index) { + int listSize = size(); + checkElementIndex(index, listSize); + int start = index * size; + int end = Math.min(start + size, list.size()); + return list.subList(start, end); + } + + @Override public int size() { + // TODO(user): refactor to common.math.IntMath.divide + int result = list.size() / size; + if (result * size != list.size()) { + result++; + } + return result; + } + + @Override public boolean isEmpty() { + return list.isEmpty(); + } + } + + private static class RandomAccessPartition extends Partition + implements RandomAccess { + RandomAccessPartition(List list, int size) { + super(list, size); + } + } + + /** + * Returns a view of the specified string as an immutable list of {@code + * Character} values. + * + * @since 7.0 + */ + @Beta public static ImmutableList charactersOf(String string) { + return new StringAsImmutableList(checkNotNull(string)); + } + + @SuppressWarnings("serial") // serialized using ImmutableList serialization + private static final class StringAsImmutableList + extends ImmutableList { + + private final String string; + + StringAsImmutableList(String string) { + this.string = string; + } + + @Override public int indexOf(@Nullable Object object) { + return (object instanceof Character) + ? string.indexOf((Character) object) : -1; + } + + @Override public int lastIndexOf(@Nullable Object object) { + return (object instanceof Character) + ? string.lastIndexOf((Character) object) : -1; + } + + @Override public ImmutableList subList( + int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); // for GWT + return charactersOf(string.substring(fromIndex, toIndex)); + } + + @Override boolean isPartialView() { + return false; + } + + @Override public Character get(int index) { + checkElementIndex(index, size()); // for GWT + return string.charAt(index); + } + + @Override public int size() { + return string.length(); + } + + @Override public boolean equals(@Nullable Object obj) { + if (!(obj instanceof List)) { + return false; + } + List list = (List) obj; + int n = string.length(); + if (n != list.size()) { + return false; + } + Iterator iterator = list.iterator(); + for (int i = 0; i < n; i++) { + Object elem = iterator.next(); + if (!(elem instanceof Character) + || ((Character) elem).charValue() != string.charAt(i)) { + return false; + } + } + return true; + } + + int hash = 0; + + @Override public int hashCode() { + int h = hash; + if (h == 0) { + h = 1; + for (int i = 0; i < string.length(); i++) { + h = h * 31 + string.charAt(i); + } + hash = h; + } + return h; + } + } + + /** + * Returns a view of the specified {@code CharSequence} as a {@code + * List}, viewing {@code sequence} as a sequence of Unicode code + * units. The view does not support any modification operations, but reflects + * any changes to the underlying character sequence. + * + * @param sequence the character sequence to view as a {@code List} of + * characters + * @return an {@code List} view of the character sequence + * @since 7.0 + */ + @Beta public static List charactersOf(CharSequence sequence) { + return new CharSequenceAsList(checkNotNull(sequence)); + } + + private static final class CharSequenceAsList + extends AbstractList { + private final CharSequence sequence; + + CharSequenceAsList(CharSequence sequence) { + this.sequence = sequence; + } + + @Override public Character get(int index) { + checkElementIndex(index, size()); // for GWT + return sequence.charAt(index); + } + + @Override public boolean contains(@Nullable Object o) { + return indexOf(o) >= 0; + } + + @Override public int indexOf(@Nullable Object o) { + if (o instanceof Character) { + char c = (Character) o; + for (int i = 0; i < sequence.length(); i++) { + if (sequence.charAt(i) == c) { + return i; + } + } + } + return -1; + } + + @Override public int lastIndexOf(@Nullable Object o) { + if (o instanceof Character) { + char c = ((Character) o).charValue(); + for (int i = sequence.length() - 1; i >= 0; i--) { + if (sequence.charAt(i) == c) { + return i; + } + } + } + return -1; + } + + @Override public int size() { + return sequence.length(); + } + + @Override public List subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); // for GWT + return charactersOf(sequence.subSequence(fromIndex, toIndex)); + } + + @Override public int hashCode() { + int hash = 1; + for (int i = 0; i < sequence.length(); i++) { + hash = hash * 31 + sequence.charAt(i); + } + return hash; + } + + @Override public boolean equals(@Nullable Object o) { + if (!(o instanceof List)) { + return false; + } + List list = (List) o; + int n = sequence.length(); + if (n != list.size()) { + return false; + } + Iterator iterator = list.iterator(); + for (int i = 0; i < n; i++) { + Object elem = iterator.next(); + if (!(elem instanceof Character) + || ((Character) elem).charValue() != sequence.charAt(i)) { + return false; + } + } + return true; + } + } + + /** + * Returns a reversed view of the specified list. For example, {@code + * Lists.reverse(Arrays.asList(1, 2, 3))} returns a list containing {@code 3, + * 2, 1}. The returned list is backed by this list, so changes in the returned + * list are reflected in this list, and vice-versa. The returned list supports + * all of the optional list operations supported by this list. + * + *

The returned list is random-access if the specified list is random + * access. + * + * @since 7.0 + */ + public static List reverse(List list) { + if (list instanceof ReverseList) { + return ((ReverseList) list).getForwardList(); + } else if (list instanceof RandomAccess) { + return new RandomAccessReverseList(list); + } else { + return new ReverseList(list); + } + } + + private static class ReverseList extends AbstractList { + private final List forwardList; + + ReverseList(List forwardList) { + this.forwardList = checkNotNull(forwardList); + } + + List getForwardList() { + return forwardList; + } + + private int reverseIndex(int index) { + int size = size(); + checkElementIndex(index, size); + return (size - 1) - index; + } + + private int reversePosition(int index) { + int size = size(); + checkPositionIndex(index, size); + return size - index; + } + + @Override public void add(int index, @Nullable T element) { + forwardList.add(reversePosition(index), element); + } + + @Override public void clear() { + forwardList.clear(); + } + + @Override public T remove(int index) { + return forwardList.remove(reverseIndex(index)); + } + + @Override protected void removeRange(int fromIndex, int toIndex) { + subList(fromIndex, toIndex).clear(); + } + + @Override public T set(int index, @Nullable T element) { + return forwardList.set(reverseIndex(index), element); + } + + @Override public T get(int index) { + return forwardList.get(reverseIndex(index)); + } + + @Override public boolean isEmpty() { + return forwardList.isEmpty(); + } + + @Override public int size() { + return forwardList.size(); + } + + @Override public boolean contains(@Nullable Object o) { + return forwardList.contains(o); + } + + @Override public boolean containsAll(Collection c) { + return forwardList.containsAll(c); + } + + @Override public List subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + return reverse(forwardList.subList( + reversePosition(toIndex), reversePosition(fromIndex))); + } + + @Override public int indexOf(@Nullable Object o) { + int index = forwardList.lastIndexOf(o); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override public int lastIndexOf(@Nullable Object o) { + int index = forwardList.indexOf(o); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override public Iterator iterator() { + return listIterator(); + } + + @Override public ListIterator listIterator(int index) { + int start = reversePosition(index); + final ListIterator forwardIterator = forwardList.listIterator(start); + return new ListIterator() { + + boolean canRemove; + boolean canSet; + + @Override public void add(T e) { + forwardIterator.add(e); + forwardIterator.previous(); + canSet = canRemove = false; + } + + @Override public boolean hasNext() { + return forwardIterator.hasPrevious(); + } + + @Override public boolean hasPrevious() { + return forwardIterator.hasNext(); + } + + @Override public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + canSet = canRemove = true; + return forwardIterator.previous(); + } + + @Override public int nextIndex() { + return reversePosition(forwardIterator.nextIndex()); + } + + @Override public T previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + canSet = canRemove = true; + return forwardIterator.next(); + } + + @Override public int previousIndex() { + return nextIndex() - 1; + } + + @Override public void remove() { + checkState(canRemove); + forwardIterator.remove(); + canRemove = canSet = false; + } + + @Override public void set(T e) { + checkState(canSet); + forwardIterator.set(e); + } + }; + } + } + + private static class RandomAccessReverseList extends ReverseList + implements RandomAccess { + RandomAccessReverseList(List forwardList) { + super(forwardList); + } + } + + /** + * An implementation of {@link List#hashCode()}. + */ + static int hashCodeImpl(List list){ + int hashCode = 1; + for (Object o : list) { + hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode()); + } + return hashCode; + } + + /** + * An implementation of {@link List#equals(Object)}. + */ + static boolean equalsImpl(List list, @Nullable Object object) { + if (object == checkNotNull(list)) { + return true; + } + if (!(object instanceof List)) { + return false; + } + + List o = (List) object; + + return list.size() == o.size() + && Iterators.elementsEqual(list.iterator(), o.iterator()); + } + + /** + * An implementation of {@link List#addAll(int, Collection)}. + */ + static boolean addAllImpl( + List list, int index, Iterable elements) { + boolean changed = false; + ListIterator listIterator = list.listIterator(index); + for (E e : elements) { + listIterator.add(e); + changed = true; + } + return changed; + } + + /** + * An implementation of {@link List#indexOf(Object)}. + */ + static int indexOfImpl(List list, @Nullable Object element){ + ListIterator listIterator = list.listIterator(); + while (listIterator.hasNext()) { + if (Objects.equal(element, listIterator.next())) { + return listIterator.previousIndex(); + } + } + return -1; + } + + /** + * An implementation of {@link List#lastIndexOf(Object)}. + */ + static int lastIndexOfImpl(List list, @Nullable Object element){ + ListIterator listIterator = list.listIterator(list.size()); + while (listIterator.hasPrevious()) { + if (Objects.equal(element, listIterator.previous())) { + return listIterator.nextIndex(); + } + } + return -1; + } + + /** + * Returns an implementation of {@link List#listIterator(int)}. + */ + static ListIterator listIteratorImpl(List list, int index) { + return new AbstractListWrapper(list).listIterator(index); + } + + /** + * An implementation of {@link List#subList(int, int)}. + */ + static List subListImpl( + final List list, int fromIndex, int toIndex) { + List wrapper; + if (list instanceof RandomAccess) { + wrapper = new RandomAccessListWrapper(list) { + @Override public ListIterator listIterator(int index) { + return backingList.listIterator(index); + } + + private static final long serialVersionUID = 0; + }; + } else { + wrapper = new AbstractListWrapper(list) { + @Override public ListIterator listIterator(int index) { + return backingList.listIterator(index); + } + + private static final long serialVersionUID = 0; + }; + } + return wrapper.subList(fromIndex, toIndex); + } + + private static class AbstractListWrapper extends AbstractList { + final List backingList; + + AbstractListWrapper(List backingList) { + this.backingList = checkNotNull(backingList); + } + + @Override public void add(int index, E element) { + backingList.add(index, element); + } + + @Override public boolean addAll(int index, Collection c) { + return backingList.addAll(index, c); + } + + @Override public E get(int index) { + return backingList.get(index); + } + + @Override public E remove(int index) { + return backingList.remove(index); + } + + @Override public E set(int index, E element) { + return backingList.set(index, element); + } + + @Override public boolean contains(Object o) { + return backingList.contains(o); + } + + @Override public int size() { + return backingList.size(); + } + } + + private static class RandomAccessListWrapper + extends AbstractListWrapper implements RandomAccess { + RandomAccessListWrapper(List backingList) { + super(backingList); + } + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + static List cast(Iterable iterable) { + return (List) iterable; + } +} diff --git a/guava/src/com/google/common/collect/MapConstraint.java b/guava/src/com/google/common/collect/MapConstraint.java new file mode 100644 index 0000000..b2f1e80 --- /dev/null +++ b/guava/src/com/google/common/collect/MapConstraint.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +/** + * A constraint on the keys and values that may be added to a {@code Map} or + * {@code Multimap}. For example, {@link MapConstraints#notNull()}, which + * prevents a map from including any null keys or values, could be implemented + * like this:

   {@code
+ *
+ *   public void checkKeyValue(Object key, Object value) {
+ *     if (key == null || value == null) {
+ *       throw new NullPointerException();
+ *     }
+ *   }}
+ * + * In order to be effective, constraints should be deterministic; that is, they + * should not depend on state that can change (such as external state, random + * variables, and time) and should only depend on the value of the passed-in key + * and value. A non-deterministic constraint cannot reliably enforce that all + * the collection's elements meet the constraint, since the constraint is only + * enforced when elements are added. + * + * @author Mike Bostock + * @see MapConstraints + * @see Constraint + * @since 3.0 + */ +@GwtCompatible +@Beta +public interface MapConstraint { + /** + * Throws a suitable {@code RuntimeException} if the specified key or value is + * illegal. Typically this is either a {@link NullPointerException}, an + * {@link IllegalArgumentException}, or a {@link ClassCastException}, though + * an application-specific exception class may be used if appropriate. + */ + void checkKeyValue(@Nullable K key, @Nullable V value); + + /** + * Returns a brief human readable description of this constraint, such as + * "Not null". + */ + @Override + String toString(); +} diff --git a/guava/src/com/google/common/collect/MapConstraints.java b/guava/src/com/google/common/collect/MapConstraints.java new file mode 100644 index 0000000..11351bb --- /dev/null +++ b/guava/src/com/google/common/collect/MapConstraints.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Factory and utilities pertaining to the {@code MapConstraint} interface. + * + * @see Constraints + * @author Mike Bostock + * @since 3.0 + */ +@Beta +@GwtCompatible +public final class MapConstraints { + private MapConstraints() {} + + /** + * Returns a constraint that verifies that neither the key nor the value is + * null. If either is null, a {@link NullPointerException} is thrown. + */ + public static MapConstraint notNull() { + return NotNullMapConstraint.INSTANCE; + } + + // enum singleton pattern + private enum NotNullMapConstraint implements MapConstraint { + INSTANCE; + + @Override + public void checkKeyValue(Object key, Object value) { + checkNotNull(key); + checkNotNull(value); + } + + @Override public String toString() { + return "Not null"; + } + } + + /** + * Returns a constrained view of the specified map, using the specified + * constraint. Any operations that add new mappings will call the provided + * constraint. However, this method does not verify that existing mappings + * satisfy the constraint. + * + *

The returned map is not serializable. + * + * @param map the map to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified map + */ + public static Map constrainedMap( + Map map, MapConstraint constraint) { + return new ConstrainedMap(map, constraint); + } + + /** + * Returns a constrained view of the specified multimap, using the specified + * constraint. Any operations that add new mappings will call the provided + * constraint. However, this method does not verify that existing mappings + * satisfy the constraint. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * + *

The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the multimap + */ + public static Multimap constrainedMultimap( + Multimap multimap, MapConstraint constraint) { + return new ConstrainedMultimap(multimap, constraint); + } + + /** + * Returns a constrained view of the specified list multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * + *

The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static ListMultimap constrainedListMultimap( + ListMultimap multimap, + MapConstraint constraint) { + return new ConstrainedListMultimap(multimap, constraint); + } + + /** + * Returns a constrained view of the specified set multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + *

The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static SetMultimap constrainedSetMultimap( + SetMultimap multimap, + MapConstraint constraint) { + return new ConstrainedSetMultimap(multimap, constraint); + } + + /** + * Returns a constrained view of the specified sorted-set multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + *

The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static SortedSetMultimap constrainedSortedSetMultimap( + SortedSetMultimap multimap, + MapConstraint constraint) { + return new ConstrainedSortedSetMultimap(multimap, constraint); + } + + /** + * Returns a constrained view of the specified entry, using the specified + * constraint. The {@link Entry#setValue} operation will be verified with the + * constraint. + * + * @param entry the entry to constrain + * @param constraint the constraint for the entry + * @return a constrained view of the specified entry + */ + private static Entry constrainedEntry( + final Entry entry, + final MapConstraint constraint) { + checkNotNull(entry); + checkNotNull(constraint); + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return entry; + } + @Override public V setValue(V value) { + constraint.checkKeyValue(getKey(), value); + return entry.setValue(value); + } + }; + } + + /** + * Returns a constrained view of the specified {@code asMap} entry, using the + * specified constraint. The {@link Entry#setValue} operation will be verified + * with the constraint, and the collection returned by {@link Entry#getValue} + * will be similarly constrained. + * + * @param entry the {@code asMap} entry to constrain + * @param constraint the constraint for the entry + * @return a constrained view of the specified entry + */ + private static Entry> constrainedAsMapEntry( + final Entry> entry, + final MapConstraint constraint) { + checkNotNull(entry); + checkNotNull(constraint); + return new ForwardingMapEntry>() { + @Override protected Entry> delegate() { + return entry; + } + @Override public Collection getValue() { + return Constraints.constrainedTypePreservingCollection( + entry.getValue(), new Constraint() { + @Override + public V checkElement(V value) { + constraint.checkKeyValue(getKey(), value); + return value; + } + }); + } + }; + } + + /** + * Returns a constrained view of the specified set of {@code asMap} entries, + * using the specified constraint. The {@link Entry#setValue} operation will + * be verified with the constraint, and the collection returned by {@link + * Entry#getValue} will be similarly constrained. The {@code add} and {@code + * addAll} operations simply forward to the underlying set, which throws an + * {@link UnsupportedOperationException} per the multimap specification. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the entries + */ + private static Set>> constrainedAsMapEntries( + Set>> entries, + MapConstraint constraint) { + return new ConstrainedAsMapEntries(entries, constraint); + } + + /** + * Returns a constrained view of the specified collection (or set) of entries, + * using the specified constraint. The {@link Entry#setValue} operation will + * be verified with the constraint, along with add operations on the returned + * collection. The {@code add} and {@code addAll} operations simply forward to + * the underlying collection, which throws an {@link + * UnsupportedOperationException} per the map and multimap specification. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the specified entries + */ + private static Collection> constrainedEntries( + Collection> entries, + MapConstraint constraint) { + if (entries instanceof Set) { + return constrainedEntrySet((Set>) entries, constraint); + } + return new ConstrainedEntries(entries, constraint); + } + + /** + * Returns a constrained view of the specified set of entries, using the + * specified constraint. The {@link Entry#setValue} operation will be verified + * with the constraint, along with add operations on the returned set. The + * {@code add} and {@code addAll} operations simply forward to the underlying + * set, which throws an {@link UnsupportedOperationException} per the map and + * multimap specification. + * + *

The returned multimap is not serializable. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the specified entries + */ + private static Set> constrainedEntrySet( + Set> entries, + MapConstraint constraint) { + return new ConstrainedEntrySet(entries, constraint); + } + + /** @see MapConstraints#constrainedMap */ + static class ConstrainedMap extends ForwardingMap { + private final Map delegate; + final MapConstraint constraint; + private transient Set> entrySet; + + ConstrainedMap( + Map delegate, MapConstraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Map delegate() { + return delegate; + } + @Override public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = + constrainedEntrySet(delegate.entrySet(), constraint); + } + return result; + } + @Override public V put(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate.put(key, value); + } + @Override public void putAll(Map map) { + delegate.putAll(checkMap(map, constraint)); + } + } + + /** + * Returns a constrained view of the specified bimap, using the specified + * constraint. Any operations that modify the bimap will have the associated + * keys and values verified with the constraint. + * + *

The returned bimap is not serializable. + * + * @param map the bimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified bimap + */ + public static BiMap constrainedBiMap( + BiMap map, MapConstraint constraint) { + return new ConstrainedBiMap(map, null, constraint); + } + + /** @see MapConstraints#constrainedBiMap */ + private static class ConstrainedBiMap extends ConstrainedMap + implements BiMap { + /* + * We could switch to racy single-check lazy init and remove volatile, but + * there's a downside. That's because this field is also written in the + * constructor. Without volatile, the constructor's write of the existing + * inverse BiMap could occur after inverse()'s read of the field's initial + * null value, leading inverse() to overwrite the existing inverse with a + * doubly indirect version. This wouldn't be catastrophic, but it's + * something to keep in mind if we make the change. + * + * Note that UnmodifiableBiMap *does* use racy single-check lazy init. + * TODO(cpovirk): pick one and standardize + */ + volatile BiMap inverse; + + ConstrainedBiMap(BiMap delegate, @Nullable BiMap inverse, + MapConstraint constraint) { + super(delegate, constraint); + this.inverse = inverse; + } + + @Override protected BiMap delegate() { + return (BiMap) super.delegate(); + } + + @Override + public V forcePut(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate().forcePut(key, value); + } + + @Override + public BiMap inverse() { + if (inverse == null) { + inverse = new ConstrainedBiMap(delegate().inverse(), this, + new InverseConstraint(constraint)); + } + return inverse; + } + + @Override public Set values() { + return delegate().values(); + } + } + + /** @see MapConstraints#constrainedBiMap */ + private static class InverseConstraint implements MapConstraint { + final MapConstraint constraint; + + public InverseConstraint(MapConstraint constraint) { + this.constraint = checkNotNull(constraint); + } + @Override + public void checkKeyValue(K key, V value) { + constraint.checkKeyValue(value, key); + } + } + + /** @see MapConstraints#constrainedMultimap */ + private static class ConstrainedMultimap + extends ForwardingMultimap { + final MapConstraint constraint; + final Multimap delegate; + transient Collection> entries; + transient Map> asMap; + + public ConstrainedMultimap(Multimap delegate, + MapConstraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + + @Override protected Multimap delegate() { + return delegate; + } + + @Override public Map> asMap() { + Map> result = asMap; + if (result == null) { + final Map> asMapDelegate = delegate.asMap(); + + asMap = result = new ForwardingMap>() { + Set>> entrySet; + Collection> values; + + @Override protected Map> delegate() { + return asMapDelegate; + } + + @Override public Set>> entrySet() { + Set>> result = entrySet; + if (result == null) { + entrySet = result = constrainedAsMapEntries( + asMapDelegate.entrySet(), constraint); + } + return result; + } + + @SuppressWarnings("unchecked") + @Override public Collection get(Object key) { + try { + Collection collection = ConstrainedMultimap.this.get((K) key); + return collection.isEmpty() ? null : collection; + } catch (ClassCastException e) { + return null; // key wasn't a K + } + } + + @Override public Collection> values() { + Collection> result = values; + if (result == null) { + values = result = new ConstrainedAsMapValues( + delegate().values(), entrySet()); + } + return result; + } + + @Override public boolean containsValue(Object o) { + return values().contains(o); + } + }; + } + return result; + } + + @Override public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = constrainedEntries(delegate.entries(), constraint); + } + return result; + } + + @Override public Collection get(final K key) { + return Constraints.constrainedTypePreservingCollection( + delegate.get(key), new Constraint() { + @Override + public V checkElement(V value) { + constraint.checkKeyValue(key, value); + return value; + } + }); + } + + @Override public boolean put(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate.put(key, value); + } + + @Override public boolean putAll(K key, Iterable values) { + return delegate.putAll(key, checkValues(key, values, constraint)); + } + + @Override public boolean putAll( + Multimap multimap) { + boolean changed = false; + for (Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + @Override public Collection replaceValues( + K key, Iterable values) { + return delegate.replaceValues(key, checkValues(key, values, constraint)); + } + } + + /** @see ConstrainedMultimap#asMap */ + private static class ConstrainedAsMapValues + extends ForwardingCollection> { + final Collection> delegate; + final Set>> entrySet; + + /** + * @param entrySet map entries, linking each key with its corresponding + * values, that already enforce the constraint + */ + ConstrainedAsMapValues(Collection> delegate, + Set>> entrySet) { + this.delegate = delegate; + this.entrySet = entrySet; + } + @Override protected Collection> delegate() { + return delegate; + } + + @Override public Iterator> iterator() { + final Iterator>> iterator = entrySet.iterator(); + return new Iterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public Collection next() { + return iterator.next().getValue(); + } + @Override + public void remove() { + iterator.remove(); + } + }; + } + + @Override public Object[] toArray() { + return standardToArray(); + } + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + @Override public boolean contains(Object o) { + return standardContains(o); + } + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + @Override public boolean remove(Object o) { + return standardRemove(o); + } + @Override public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + @Override public boolean retainAll(Collection c) { + return standardRetainAll(c); + } + } + + /** @see MapConstraints#constrainedEntries */ + private static class ConstrainedEntries + extends ForwardingCollection> { + final MapConstraint constraint; + final Collection> entries; + + ConstrainedEntries(Collection> entries, + MapConstraint constraint) { + this.entries = entries; + this.constraint = constraint; + } + @Override protected Collection> delegate() { + return entries; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = entries.iterator(); + return new ForwardingIterator>() { + @Override public Entry next() { + return constrainedEntry(iterator.next(), constraint); + } + @Override protected Iterator> delegate() { + return iterator; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return standardToArray(); + } + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + @Override public boolean remove(Object o) { + return Maps.removeEntryImpl(delegate(), o); + } + @Override public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + @Override public boolean retainAll(Collection c) { + return standardRetainAll(c); + } + } + + /** @see MapConstraints#constrainedEntrySet */ + static class ConstrainedEntrySet + extends ConstrainedEntries implements Set> { + ConstrainedEntrySet(Set> entries, + MapConstraint constraint) { + super(entries, constraint); + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public boolean equals(@Nullable Object object) { + return Sets.equalsImpl(this, object); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** @see MapConstraints#constrainedAsMapEntries */ + static class ConstrainedAsMapEntries + extends ForwardingSet>> { + private final MapConstraint constraint; + private final Set>> entries; + + ConstrainedAsMapEntries(Set>> entries, + MapConstraint constraint) { + this.entries = entries; + this.constraint = constraint; + } + + @Override protected Set>> delegate() { + return entries; + } + + @Override public Iterator>> iterator() { + final Iterator>> iterator = entries.iterator(); + return new ForwardingIterator>>() { + @Override public Entry> next() { + return constrainedAsMapEntry(iterator.next(), constraint); + } + @Override protected Iterator>> delegate() { + return iterator; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return standardToArray(); + } + + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + + @Override public boolean equals(@Nullable Object object) { + return standardEquals(object); + } + + @Override public int hashCode() { + return standardHashCode(); + } + + @Override public boolean remove(Object o) { + return Maps.removeEntryImpl(delegate(), o); + } + + @Override public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + + @Override public boolean retainAll(Collection c) { + return standardRetainAll(c); + } + } + + private static class ConstrainedListMultimap + extends ConstrainedMultimap implements ListMultimap { + ConstrainedListMultimap(ListMultimap delegate, + MapConstraint constraint) { + super(delegate, constraint); + } + @Override public List get(K key) { + return (List) super.get(key); + } + @Override public List removeAll(Object key) { + return (List) super.removeAll(key); + } + @Override public List replaceValues( + K key, Iterable values) { + return (List) super.replaceValues(key, values); + } + } + + private static class ConstrainedSetMultimap + extends ConstrainedMultimap implements SetMultimap { + ConstrainedSetMultimap(SetMultimap delegate, + MapConstraint constraint) { + super(delegate, constraint); + } + @Override public Set get(K key) { + return (Set) super.get(key); + } + @Override public Set> entries() { + return (Set>) super.entries(); + } + @Override public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + @Override public Set replaceValues( + K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + } + + private static class ConstrainedSortedSetMultimap + extends ConstrainedSetMultimap implements SortedSetMultimap { + ConstrainedSortedSetMultimap(SortedSetMultimap delegate, + MapConstraint constraint) { + super(delegate, constraint); + } + @Override public SortedSet get(K key) { + return (SortedSet) super.get(key); + } + @Override public SortedSet removeAll(Object key) { + return (SortedSet) super.removeAll(key); + } + @Override public SortedSet replaceValues( + K key, Iterable values) { + return (SortedSet) super.replaceValues(key, values); + } + @Override + public Comparator valueComparator() { + return ((SortedSetMultimap) delegate()).valueComparator(); + } + } + + private static Collection checkValues(K key, + Iterable values, + MapConstraint constraint) { + Collection copy = Lists.newArrayList(values); + for (V value : copy) { + constraint.checkKeyValue(key, value); + } + return copy; + } + + private static Map checkMap(Map map, + MapConstraint constraint) { + Map copy = new LinkedHashMap(map); + for (Entry entry : copy.entrySet()) { + constraint.checkKeyValue(entry.getKey(), entry.getValue()); + } + return copy; + } +} diff --git a/guava/src/com/google/common/collect/MapDifference.java b/guava/src/com/google/common/collect/MapDifference.java new file mode 100644 index 0000000..484cf67 --- /dev/null +++ b/guava/src/com/google/common/collect/MapDifference.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An object representing the differences between two maps. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface MapDifference { + /** + * Returns {@code true} if there are no differences between the two maps; + * that is, if the maps are equal. + */ + boolean areEqual(); + + /** + * Returns an unmodifiable map containing the entries from the left map whose + * keys are not present in the right map. + */ + Map entriesOnlyOnLeft(); + + /** + * Returns an unmodifiable map containing the entries from the right map whose + * keys are not present in the left map. + */ + Map entriesOnlyOnRight(); + + /** + * Returns an unmodifiable map containing the entries that appear in both + * maps; that is, the intersection of the two maps. + */ + Map entriesInCommon(); + + /** + * Returns an unmodifiable map describing keys that appear in both maps, but + * with different values. + */ + Map> entriesDiffering(); + + /** + * Compares the specified object with this instance for equality. Returns + * {@code true} if the given object is also a {@code MapDifference} and the + * values returned by the {@link #entriesOnlyOnLeft()}, {@link + * #entriesOnlyOnRight()}, {@link #entriesInCommon()} and {@link + * #entriesDiffering()} of the two instances are equal. + */ + @Override + boolean equals(@Nullable Object object); + + /** + * Returns the hash code for this instance. This is defined as the hash code + * of

   {@code
+   *
+   *   Arrays.asList(entriesOnlyOnLeft(), entriesOnlyOnRight(),
+   *       entriesInCommon(), entriesDiffering())}
+ */ + @Override + int hashCode(); + + /** + * A difference between the mappings from two maps with the same key. The + * {@link #leftValue} and {@link #rightValue} are not equal, and one but not + * both of them may be null. + * + * @since 2.0 (imported from Google Collections Library) + */ + interface ValueDifference { + /** + * Returns the value from the left map (possibly null). + */ + V leftValue(); + + /** + * Returns the value from the right map (possibly null). + */ + V rightValue(); + + /** + * Two instances are considered equal if their {@link #leftValue()} + * values are equal and their {@link #rightValue()} values are also equal. + */ + @Override boolean equals(@Nullable Object other); + + /** + * The hash code equals the value + * {@code Arrays.asList(leftValue(), rightValue()).hashCode()}. + */ + @Override int hashCode(); + } + +} diff --git a/guava/src/com/google/common/collect/MapMaker.java b/guava/src/com/google/common/collect/MapMaker.java new file mode 100644 index 0000000..d96e237 --- /dev/null +++ b/guava/src/com/google/common/collect/MapMaker.java @@ -0,0 +1,887 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Objects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Ticker; +import com.google.common.collect.ComputingConcurrentHashMap.ComputingMapAdapter; +import com.google.common.collect.MapMakerInternalMap.Strength; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +/** + *

A builder of {@link ConcurrentMap} instances having any combination of the following features: + * + *

    + *
  • keys or values automatically wrapped in {@linkplain WeakReference weak} or {@linkplain + * SoftReference soft} references + *
  • notification of evicted (or otherwise removed) entries + *
  • on-demand computation of values for keys not already present + *
+ * + *

Usage example:

   {@code
+ *
+ *   ConcurrentMap graphs = new MapMaker()
+ *       .concurrencyLevel(4)
+ *       .weakKeys()
+ *       .makeComputingMap(
+ *           new Function() {
+ *             public Graph apply(Key key) {
+ *               return createExpensiveGraph(key);
+ *             }
+ *           });}
+ * + * These features are all optional; {@code new MapMaker().makeMap()} returns a valid concurrent map + * that behaves similarly to a {@link ConcurrentHashMap}. + * + *

The returned map is implemented as a hash table with similar performance characteristics to + * {@link ConcurrentHashMap}. It supports all optional operations of the {@code ConcurrentMap} + * interface. It does not permit null keys or values. + * + *

Note: by default, the returned map uses equality comparisons (the {@link Object#equals + * equals} method) to determine equality for keys or values. However, if {@link #weakKeys} or {@link + * #softKeys} was specified, the map uses identity ({@code ==}) comparisons instead for keys. + * Likewise, if {@link #weakValues} or {@link #softValues} was specified, the map uses identity + * comparisons for values. + * + *

The view collections of the returned map have weakly consistent iterators. This means + * that they are safe for concurrent use, but if other threads modify the map after the iterator is + * created, it is undefined which of these changes, if any, are reflected in that iterator. These + * iterators never throw {@link ConcurrentModificationException}. + * + *

If soft or weak references were requested, it is possible for a key or value present in the + * the map to be reclaimed by the garbage collector. If this happens, the entry automatically + * disappears from the map. A partially-reclaimed entry is never exposed to the user. Any {@link + * java.util.Map.Entry} instance retrieved from the map's {@linkplain Map#entrySet entry set} is a + * snapshot of that entry's state at the time of retrieval; such entries do, however, support {@link + * java.util.Map.Entry#setValue}, which simply calls {@link Map#put} on the entry's key. + * + *

The maps produced by {@code MapMaker} are serializable, and the deserialized maps retain all + * the configuration properties of the original map. During deserialization, if the original map had + * used soft or weak references, the entries are reconstructed as they were, but it's not unlikely + * they'll be quickly garbage-collected before they are ever accessed. + * + *

{@code new MapMaker().weakKeys().makeMap()} is a recommended replacement for {@link + * java.util.WeakHashMap}, but note that it compares keys using object identity whereas {@code + * WeakHashMap} uses {@link Object#equals}. + * + * @author Bob Lee + * @author Charles Fry + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class MapMaker extends GenericMapMaker { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 4; + private static final int DEFAULT_EXPIRATION_NANOS = 0; + + static final int UNSET_INT = -1; + + // TODO(kevinb): dispense with this after benchmarking + boolean useCustomMap; + + int initialCapacity = UNSET_INT; + int concurrencyLevel = UNSET_INT; + int maximumSize = UNSET_INT; + + Strength keyStrength; + Strength valueStrength; + + long expireAfterWriteNanos = UNSET_INT; + long expireAfterAccessNanos = UNSET_INT; + + RemovalCause nullRemovalCause; + + Equivalence keyEquivalence; + + Ticker ticker; + + /** + * Constructs a new {@code MapMaker} instance with default settings, including strong keys, strong + * values, and no automatic eviction of any kind. + */ + public MapMaker() {} + + /** + * Sets a custom {@code Equivalence} strategy for comparing keys. + * + *

By default, the map uses {@link Equivalence#identity} to determine key equality when + * {@link #weakKeys} or {@link #softKeys} is specified, and {@link Equivalence#equals()} + * otherwise. The only place this is used is in {@link Interners.WeakInterner}. + */ + @GwtIncompatible("To be supported") + @Override + MapMaker keyEquivalence(Equivalence equivalence) { + checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence); + keyEquivalence = checkNotNull(equivalence); + this.useCustomMap = true; + return this; + } + + Equivalence getKeyEquivalence() { + return firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence()); + } + + /** + * Sets the minimum total size for the internal hash tables. For example, if the initial capacity + * is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each + * having a hash table of size eight. Providing a large enough estimate at construction time + * avoids the need for expensive resizing operations later, but setting this value unnecessarily + * high wastes memory. + * + * @throws IllegalArgumentException if {@code initialCapacity} is negative + * @throws IllegalStateException if an initial capacity was already set + */ + @Override + public MapMaker initialCapacity(int initialCapacity) { + checkState(this.initialCapacity == UNSET_INT, "initial capacity was already set to %s", + this.initialCapacity); + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + int getInitialCapacity() { + return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; + } + + /** + * Specifies the maximum number of entries the map may contain. Note that the map may evict an + * entry before this limit is exceeded. As the map size grows close to the maximum, the map + * evicts entries that are less likely to be used again. For example, the map may evict an entry + * because it hasn't been used recently or very often. + * + *

When {@code size} is zero, elements can be successfully added to the map, but are evicted + * immediately. This has the same effect as invoking {@link #expireAfterWrite + * expireAfterWrite}{@code (0, unit)} or {@link #expireAfterAccess expireAfterAccess}{@code (0, + * unit)}. It can be useful in testing, or to disable caching temporarily without a code change. + * + *

Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}. + * + * @param size the maximum size of the map + * @throws IllegalArgumentException if {@code size} is negative + * @throws IllegalStateException if a maximum size was already set + * @deprecated Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}, with {@link #maximumSize} being + * replaced by {@link com.google.common.cache.CacheBuilder#maximumSize}. Note that {@code + * CacheBuilder} is simply an enhanced API for an implementation which was branched from + * {@code MapMaker}. + */ + @Deprecated + @Override + MapMaker maximumSize(int size) { + checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s", + this.maximumSize); + checkArgument(size >= 0, "maximum size must not be negative"); + this.maximumSize = size; + this.useCustomMap = true; + if (maximumSize == 0) { + // SIZE trumps EXPIRED + this.nullRemovalCause = RemovalCause.SIZE; + } + return this; + } + + /** + * Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The + * table is internally partitioned to try to permit the indicated number of concurrent updates + * without contention. Because assignment of entries to these partitions is not necessarily + * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to + * accommodate as many threads as will ever concurrently modify the table. Using a significantly + * higher value than you need can waste space and time, and a significantly lower value can lead + * to thread contention. But overestimates and underestimates within an order of magnitude do not + * usually have much noticeable impact. A value of one permits only one thread to modify the map + * at a time, but since read operations can proceed concurrently, this still yields higher + * concurrency than full synchronization. Defaults to 4. + * + *

Note: Prior to Guava release 9.0, the default was 16. It is possible the default will + * change again in the future. If you care about this value, you should always choose it + * explicitly. + * + * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive + * @throws IllegalStateException if a concurrency level was already set + */ + @Override + public MapMaker concurrencyLevel(int concurrencyLevel) { + checkState(this.concurrencyLevel == UNSET_INT, "concurrency level was already set to %s", + this.concurrencyLevel); + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + int getConcurrencyLevel() { + return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; + } + + /** + * Specifies that each key (not value) stored in the map should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of keys, which is a technical violation of the {@link Map} + * specification, and may not be what you expect. + * + * @throws IllegalStateException if the key strength was already set + * @see WeakReference + */ + @GwtIncompatible("java.lang.ref.WeakReference") + @Override + public MapMaker weakKeys() { + return setKeyStrength(Strength.WEAK); + } + + /** + * This method is broken. Maps with soft keys offer no functional advantage over maps with + * weak keys, and they waste memory by keeping unreachable elements in the map. If your goal is to + * create a memory-sensitive map, then consider using soft values instead. + * + *

Specifies that each key (not value) stored in the map should be wrapped in a + * {@link SoftReference} (by default, strong references are used). Softly-referenced objects will + * be garbage-collected in a globally least-recently-used manner, in response to memory + * demand. + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of keys, which is a technical violation of the {@link Map} + * specification, and may not be what you expect. + * + * @throws IllegalStateException if the key strength was already set + * @see SoftReference + * @deprecated use {@link #softValues} to create a memory-sensitive map, or {@link #weakKeys} to + * create a map that doesn't hold strong references to the keys. + * This method is scheduled for deletion in January 2013. + */ + @Deprecated + @GwtIncompatible("java.lang.ref.SoftReference") + @Override + public MapMaker softKeys() { + return setKeyStrength(Strength.SOFT); + } + + MapMaker setKeyStrength(Strength strength) { + checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); + keyStrength = checkNotNull(strength); + if (strength != Strength.STRONG) { + // STRONG could be used during deserialization. + useCustomMap = true; + } + return this; + } + + Strength getKeyStrength() { + return firstNonNull(keyStrength, Strength.STRONG); + } + + /** + * Specifies that each value (not key) stored in the map should be wrapped in a + * {@link WeakReference} (by default, strong references are used). + * + *

Weak values will be garbage collected once they are weakly reachable. This makes them a poor + * candidate for caching; consider {@link #softValues} instead. + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of values. This technically violates the specifications of + * the methods {@link Map#containsValue containsValue}, + * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)} and + * {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}, and may not be what you + * expect. + * + * @throws IllegalStateException if the value strength was already set + * @see WeakReference + */ + @GwtIncompatible("java.lang.ref.WeakReference") + @Override + public MapMaker weakValues() { + return setValueStrength(Strength.WEAK); + } + + /** + * Specifies that each value (not key) stored in the map should be wrapped in a + * {@link SoftReference} (by default, strong references are used). Softly-referenced objects will + * be garbage-collected in a globally least-recently-used manner, in response to memory + * demand. + * + *

Warning: in most circumstances it is better to set a per-cache {@linkplain + * #maximumSize maximum size} instead of using soft references. You should only use this method if + * you are well familiar with the practical consequences of soft references. + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of values. This technically violates the specifications of + * the methods {@link Map#containsValue containsValue}, + * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)} and + * {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}, and may not be what you + * expect. + * + * @throws IllegalStateException if the value strength was already set + * @see SoftReference + */ + @GwtIncompatible("java.lang.ref.SoftReference") + @Override + public MapMaker softValues() { + return setValueStrength(Strength.SOFT); + } + + MapMaker setValueStrength(Strength strength) { + checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); + valueStrength = checkNotNull(strength); + if (strength != Strength.STRONG) { + // STRONG could be used during deserialization. + useCustomMap = true; + } + return this; + } + + Strength getValueStrength() { + return firstNonNull(valueStrength, Strength.STRONG); + } + + /** + * Specifies that each entry should be automatically removed from the map once a fixed duration + * has elapsed after the entry's creation, or the most recent replacement of its value. + * + *

When {@code duration} is zero, elements can be successfully added to the map, but are + * evicted immediately. This has a very similar effect to invoking {@link #maximumSize + * maximumSize}{@code (0)}. It can be useful in testing, or to disable caching temporarily without + * a code change. + * + *

Expired entries may be counted by {@link Map#size}, but will never be visible to read or + * write operations. Expired entries are currently cleaned up during write operations, or during + * occasional read operations in the absense of writes; though this behavior may change in the + * future. + * + * @param duration the length of time after an entry is created that it should be automatically + * removed + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to live or time to idle was already set + * @deprecated Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}, with {@link #expireAfterWrite} being + * replaced by {@link com.google.common.cache.CacheBuilder#expireAfterWrite}. Note that {@code + * CacheBuilder} is simply an enhanced API for an implementation which was branched from + * {@code MapMaker}. + */ + @Deprecated + @Override + MapMaker expireAfterWrite(long duration, TimeUnit unit) { + checkExpiration(duration, unit); + this.expireAfterWriteNanos = unit.toNanos(duration); + if (duration == 0 && this.nullRemovalCause == null) { + // SIZE trumps EXPIRED + this.nullRemovalCause = RemovalCause.EXPIRED; + } + useCustomMap = true; + return this; + } + + private void checkExpiration(long duration, TimeUnit unit) { + checkState(expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns", + expireAfterWriteNanos); + checkState(expireAfterAccessNanos == UNSET_INT, "expireAfterAccess was already set to %s ns", + expireAfterAccessNanos); + checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); + } + + long getExpireAfterWriteNanos() { + return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; + } + + /** + * Specifies that each entry should be automatically removed from the map once a fixed duration + * has elapsed after the entry's last read or write access. + * + *

When {@code duration} is zero, elements can be successfully added to the map, but are + * evicted immediately. This has a very similar effect to invoking {@link #maximumSize + * maximumSize}{@code (0)}. It can be useful in testing, or to disable caching temporarily without + * a code change. + * + *

Expired entries may be counted by {@link Map#size}, but will never be visible to read or + * write operations. Expired entries are currently cleaned up during write operations, or during + * occasional read operations in the absense of writes; though this behavior may change in the + * future. + * + * @param duration the length of time after an entry is last accessed that it should be + * automatically removed + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to idle or time to live was already set + * @deprecated Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}, with {@link #expireAfterAccess} being + * replaced by {@link com.google.common.cache.CacheBuilder#expireAfterAccess}. Note that + * {@code CacheBuilder} is simply an enhanced API for an implementation which was branched + * from {@code MapMaker}. + */ + @Deprecated + @GwtIncompatible("To be supported") + @Override + MapMaker expireAfterAccess(long duration, TimeUnit unit) { + checkExpiration(duration, unit); + this.expireAfterAccessNanos = unit.toNanos(duration); + if (duration == 0 && this.nullRemovalCause == null) { + // SIZE trumps EXPIRED + this.nullRemovalCause = RemovalCause.EXPIRED; + } + useCustomMap = true; + return this; + } + + long getExpireAfterAccessNanos() { + return (expireAfterAccessNanos == UNSET_INT) + ? DEFAULT_EXPIRATION_NANOS : expireAfterAccessNanos; + } + + Ticker getTicker() { + return firstNonNull(ticker, Ticker.systemTicker()); + } + + /** + * Specifies a listener instance, which all maps built using this {@code MapMaker} will notify + * each time an entry is removed from the map by any means. + * + *

Each map built by this map maker after this method is called invokes the supplied listener + * after removing an element for any reason (see removal causes in {@link RemovalCause}). It will + * invoke the listener during invocations of any of that map's public methods (even read-only + * methods). + * + *

Important note: Instead of returning this as a {@code MapMaker} instance, + * this method returns {@code GenericMapMaker}. From this point on, either the original + * reference or the returned reference may be used to complete configuration and build the map, + * but only the "generic" one is type-safe. That is, it will properly prevent you from building + * maps whose key or value types are incompatible with the types accepted by the listener already + * provided; the {@code MapMaker} type cannot do this. For best results, simply use the standard + * method-chaining idiom, as illustrated in the documentation at top, configuring a {@code + * MapMaker} and building your {@link Map} all in a single statement. + * + *

Warning: if you ignore the above advice, and use this {@code MapMaker} to build a map + * or cache whose key or value type is incompatible with the listener, you will likely experience + * a {@link ClassCastException} at some undefined point in the future. + * + * @throws IllegalStateException if a removal listener was already set + * @deprecated Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}, with {@link #removalListener} being + * replaced by {@link com.google.common.cache.CacheBuilder#removalListener}. Note that {@code + * CacheBuilder} is simply an enhanced API for an implementation which was branched from + * {@code MapMaker}. + */ + @Deprecated + @GwtIncompatible("To be supported") + GenericMapMaker removalListener(RemovalListener listener) { + checkState(this.removalListener == null); + + // safely limiting the kinds of maps this can produce + @SuppressWarnings("unchecked") + GenericMapMaker me = (GenericMapMaker) this; + me.removalListener = checkNotNull(listener); + useCustomMap = true; + return me; + } + + /** + * Builds a thread-safe map, without on-demand computation of values. This method does not alter + * the state of this {@code MapMaker} instance, so it can be invoked again to create multiple + * independent maps. + * + *

The bulk operations {@code putAll}, {@code equals}, and {@code clear} are not guaranteed to + * be performed atomically on the returned map. Additionally, {@code size} and {@code + * containsValue} are implemented as bulk read operations, and thus may fail to observe concurrent + * writes. + * + * @return a serializable concurrent map having the requested features + */ + @Override + public ConcurrentMap makeMap() { + if (!useCustomMap) { + return new ConcurrentHashMap(getInitialCapacity(), 0.75f, getConcurrencyLevel()); + } + return (nullRemovalCause == null) + ? new MapMakerInternalMap(this) + : new NullConcurrentMap(this); + } + + /** + * Returns a MapMakerInternalMap for the benefit of internal callers that use features of + * that class not exposed through ConcurrentMap. + */ + @Override + @GwtIncompatible("MapMakerInternalMap") + MapMakerInternalMap makeCustomMap() { + return new MapMakerInternalMap(this); + } + + /** + * Builds a map that supports atomic, on-demand computation of values. {@link Map#get} either + * returns an already-computed value for the given key, atomically computes it using the supplied + * function, or, if another thread is currently computing the value for this key, simply waits for + * that thread to finish and returns its computed value. Note that the function may be executed + * concurrently by multiple threads, but only for distinct keys. + * + *

New code should use {@link com.google.common.cache.CacheBuilder}, which supports + * {@linkplain com.google.common.cache.CacheStats statistics} collection, introduces the + * {@link com.google.common.cache.CacheLoader} interface for loading entries into the cache + * (allowing checked exceptions to be thrown in the process), and more cleanly separates + * computation from the cache's {@code Map} view. + * + *

If an entry's value has not finished computing yet, query methods besides {@code get} return + * immediately as if an entry doesn't exist. In other words, an entry isn't externally visible + * until the value's computation completes. + * + *

{@link Map#get} on the returned map will never return {@code null}. It may throw: + * + *

    + *
  • {@link NullPointerException} if the key is null or the computing function returns a null + * result + *
  • {@link ComputationException} if an exception was thrown by the computing function. If that + * exception is already of type {@link ComputationException} it is propagated directly; otherwise + * it is wrapped. + *
+ * + *

Note: Callers of {@code get} must ensure that the key argument is of type + * {@code K}. The {@code get} method accepts {@code Object}, so the key type is not checked at + * compile time. Passing an object of a type other than {@code K} can result in that object being + * unsafely passed to the computing function as type {@code K}, and unsafely stored in the map. + * + *

If {@link Map#put} is called before a computation completes, other threads waiting on the + * computation will wake up and return the stored value. + * + *

This method does not alter the state of this {@code MapMaker} instance, so it can be invoked + * again to create multiple independent maps. + * + *

Insertion, removal, update, and access operations on the returned map safely execute + * concurrently by multiple threads. Iterators on the returned map are weakly consistent, + * returning elements reflecting the state of the map at some point at or since the creation of + * the iterator. They do not throw {@link ConcurrentModificationException}, and may proceed + * concurrently with other operations. + * + *

The bulk operations {@code putAll}, {@code equals}, and {@code clear} are not guaranteed to + * be performed atomically on the returned map. Additionally, {@code size} and {@code + * containsValue} are implemented as bulk read operations, and thus may fail to observe concurrent + * writes. + * + * @param computingFunction the function used to compute new values + * @return a serializable concurrent map having the requested features + * @deprecated Caching functionality in {@code MapMaker} is being moved to + * {@link com.google.common.cache.CacheBuilder}, with {@link #makeComputingMap} being replaced + * by {@link com.google.common.cache.CacheBuilder#build}. See the + * MapMaker + * Migration Guide for more details. + * This method is scheduled for deletion in February 2013. + */ + @Deprecated + @Override + public ConcurrentMap makeComputingMap( + Function computingFunction) { + return (nullRemovalCause == null) + ? new ComputingMapAdapter(this, computingFunction) + : new NullComputingConcurrentMap(this, computingFunction); + } + + /** + * Returns a string representation for this MapMaker instance. The exact form of the returned + * string is not specificed. + */ + @Override + public String toString() { + Objects.ToStringHelper s = Objects.toStringHelper(this); + if (initialCapacity != UNSET_INT) { + s.add("initialCapacity", initialCapacity); + } + if (concurrencyLevel != UNSET_INT) { + s.add("concurrencyLevel", concurrencyLevel); + } + if (maximumSize != UNSET_INT) { + s.add("maximumSize", maximumSize); + } + if (expireAfterWriteNanos != UNSET_INT) { + s.add("expireAfterWrite", expireAfterWriteNanos + "ns"); + } + if (expireAfterAccessNanos != UNSET_INT) { + s.add("expireAfterAccess", expireAfterAccessNanos + "ns"); + } + if (keyStrength != null) { + s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString())); + } + if (valueStrength != null) { + s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString())); + } + if (keyEquivalence != null) { + s.addValue("keyEquivalence"); + } + if (removalListener != null) { + s.addValue("removalListener"); + } + return s.toString(); + } + + /** + * An object that can receive a notification when an entry is removed from a map. The removal + * resulting in notification could have occured to an entry being manually removed or replaced, or + * due to eviction resulting from timed expiration, exceeding a maximum size, or garbage + * collection. + * + *

An instance may be called concurrently by multiple threads to process different entries. + * Implementations of this interface should avoid performing blocking calls or synchronizing on + * shared resources. + * + * @param the most general type of keys this listener can listen for; for + * example {@code Object} if any key is acceptable + * @param the most general type of values this listener can listen for; for + * example {@code Object} if any key is acceptable + */ + interface RemovalListener { + /** + * Notifies the listener that a removal occurred at some point in the past. + */ + void onRemoval(RemovalNotification notification); + } + + /** + * A notification of the removal of a single entry. The key or value may be null if it was already + * garbage collected. + * + *

Like other {@code Map.Entry} instances associated with MapMaker, this class holds strong + * references to the key and value, regardless of the type of references the map may be using. + */ + static final class RemovalNotification extends ImmutableEntry { + private static final long serialVersionUID = 0; + + private final RemovalCause cause; + + RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) { + super(key, value); + this.cause = cause; + } + + /** + * Returns the cause for which the entry was removed. + */ + public RemovalCause getCause() { + return cause; + } + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}). + */ + public boolean wasEvicted() { + return cause.wasEvicted(); + } + } + + /** + * The reason why an entry was removed. + */ + enum RemovalCause { + /** + * The entry was manually removed by the user. This can result from the user invoking + * {@link Map#remove}, {@link ConcurrentMap#remove}, or {@link java.util.Iterator#remove}. + */ + EXPLICIT { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry itself was not actually removed, but its value was replaced by the user. This can + * result from the user invoking {@link Map#put}, {@link Map#putAll}, + * {@link ConcurrentMap#replace(Object, Object)}, or + * {@link ConcurrentMap#replace(Object, Object, Object)}. + */ + REPLACED { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry was removed automatically because its key or value was garbage-collected. This + * can occur when using {@link #softKeys}, {@link #softValues}, {@link #weakKeys}, or {@link + * #weakValues}. + */ + COLLECTED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry's expiration timestamp has passed. This can occur when using {@link + * #expireAfterWrite} or {@link #expireAfterAccess}. + */ + EXPIRED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry was evicted due to size constraints. This can occur when using {@link + * #maximumSize}. + */ + SIZE { + @Override + boolean wasEvicted() { + return true; + } + }; + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link #EXPLICIT} nor {@link #REPLACED}). + */ + abstract boolean wasEvicted(); + } + + /** A map that is always empty and evicts on insertion. */ + static class NullConcurrentMap extends AbstractMap + implements ConcurrentMap, Serializable { + private static final long serialVersionUID = 0; + + private final RemovalListener removalListener; + private final RemovalCause removalCause; + + NullConcurrentMap(MapMaker mapMaker) { + removalListener = mapMaker.getRemovalListener(); + removalCause = mapMaker.nullRemovalCause; + } + + // implements ConcurrentMap + + @Override + public boolean containsKey(@Nullable Object key) { + return false; + } + + @Override + public boolean containsValue(@Nullable Object value) { + return false; + } + + @Override + public V get(@Nullable Object key) { + return null; + } + + void notifyRemoval(K key, V value) { + RemovalNotification notification = + new RemovalNotification(key, value, removalCause); + removalListener.onRemoval(notification); + } + + @Override + public V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); + notifyRemoval(key, value); + return null; + } + + @Override + public V putIfAbsent(K key, V value) { + return put(key, value); + } + + @Override + public V remove(@Nullable Object key) { + return null; + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + return false; + } + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + return null; + } + + @Override + public boolean replace(K key, @Nullable V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(newValue); + return false; + } + + @Override + public Set> entrySet() { + return Collections.emptySet(); + } + } + + /** Computes on retrieval and evicts the result. */ + static final class NullComputingConcurrentMap extends NullConcurrentMap { + private static final long serialVersionUID = 0; + + final Function computingFunction; + + NullComputingConcurrentMap( + MapMaker mapMaker, Function computingFunction) { + super(mapMaker); + this.computingFunction = checkNotNull(computingFunction); + } + + @SuppressWarnings("unchecked") // unsafe, which is why Cache is preferred + @Override + public V get(Object k) { + K key = (K) k; + V value = compute(key); + checkNotNull(value, computingFunction + " returned null for key " + key + "."); + notifyRemoval(key, value); + return value; + } + + private V compute(K key) { + checkNotNull(key); + try { + return computingFunction.apply(key); + } catch (ComputationException e) { + throw e; + } catch (Throwable t) { + throw new ComputationException(t); + } + } + } + +} diff --git a/guava/src/com/google/common/collect/MapMakerInternalMap.java b/guava/src/com/google/common/collect/MapMakerInternalMap.java new file mode 100644 index 0000000..ac7b371 --- /dev/null +++ b/guava/src/com/google/common/collect/MapMakerInternalMap.java @@ -0,0 +1,4081 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Equivalence; +import com.google.common.base.Ticker; +import com.google.common.collect.GenericMapMaker.NullListener; +import com.google.common.collect.MapMaker.RemovalCause; +import com.google.common.collect.MapMaker.RemovalListener; +import com.google.common.collect.MapMaker.RemovalNotification; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractQueue; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * The concurrent hash map implementation built by {@link MapMaker}. + * + *

This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java. + * + * @author Bob Lee + * @author Charles Fry + * @author Doug Lea ({@code ConcurrentHashMap}) + */ +class MapMakerInternalMap + extends AbstractMap implements ConcurrentMap, Serializable { + + /* + * The basic strategy is to subdivide the table among Segments, each of which itself is a + * concurrently readable hash table. The map supports non-blocking reads and concurrent writes + * across different segments. + * + * If a maximum size is specified, a best-effort bounding is performed per segment, using a + * page-replacement algorithm to determine which entries to evict when the capacity has been + * exceeded. + * + * The page replacement algorithm's data structures are kept casually consistent with the map. The + * ordering of writes to a segment is sequentially consistent. An update to the map and recording + * of reads may not be immediately reflected on the algorithm's data structures. These structures + * are guarded by a lock and operations are applied in batches to avoid lock contention. The + * penalty of applying the batches is spread across threads so that the amortized cost is slightly + * higher than performing just the operation without enforcing the capacity constraint. + * + * This implementation uses a per-segment queue to record a memento of the additions, removals, + * and accesses that were performed on the map. The queue is drained on writes and when it exceeds + * its capacity threshold. + * + * The Least Recently Used page replacement algorithm was chosen due to its simplicity, high hit + * rate, and ability to be implemented with O(1) time complexity. The initial LRU implementation + * operates per-segment rather than globally for increased implementation simplicity. We expect + * the cache hit rate to be similar to that of a global LRU algorithm. + */ + + // Constants + + /** + * 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 to ensure that entries are + * indexable using ints. + */ + static final int MAXIMUM_CAPACITY = Ints.MAX_POWER_OF_TWO; + + /** The maximum number of segments to allow; used to bound constructor arguments. */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** Number of (unsynchronized) retries in the containsValue method. */ + static final int CONTAINS_VALUE_RETRIES = 3; + + /** + * Number of cache access operations that can be buffered per segment before the cache's recency + * ordering information is updated. This is used to avoid lock contention by recording a memento + * of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs. + * + *

This must be a (2^n)-1 as it is used as a mask. + */ + static final int DRAIN_THRESHOLD = 0x3F; + + /** + * Maximum number of entries to be drained in a single cleanup run. This applies independently to + * the cleanup queue and both reference queues. + */ + // TODO(fry): empirically optimize this + static final int DRAIN_MAX = 16; + + static final long CLEANUP_EXECUTOR_DELAY_SECS = 60; + + // Fields + + private static final Logger logger = Logger.getLogger(MapMakerInternalMap.class.getName()); + + /** + * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose + * the segment. + */ + final transient int segmentMask; + + /** + * Shift value for indexing within segments. Helps prevent entries that end up in the same segment + * from also ending up in the same bucket. + */ + final transient int segmentShift; + + /** The segments, each of which is a specialized hash table. */ + final transient Segment[] segments; + + /** The concurrency level. */ + final int concurrencyLevel; + + /** Strategy for comparing keys. */ + final Equivalence keyEquivalence; + + /** Strategy for comparing values. */ + final Equivalence valueEquivalence; + + /** Strategy for referencing keys. */ + final Strength keyStrength; + + /** Strategy for referencing values. */ + final Strength valueStrength; + + /** The maximum size of this map. MapMaker.UNSET_INT if there is no maximum. */ + final int maximumSize; + + /** How long after the last access to an entry the map will retain that entry. */ + final long expireAfterAccessNanos; + + /** How long after the last write to an entry the map will retain that entry. */ + final long expireAfterWriteNanos; + + /** Entries waiting to be consumed by the removal listener. */ + // TODO(fry): define a new type which creates event objects and automates the clear logic + final Queue> removalNotificationQueue; + + /** + * A listener that is invoked when an entry is removed due to expiration or garbage collection of + * soft/weak entries. + */ + final RemovalListener removalListener; + + /** Factory used to create new entries. */ + final transient EntryFactory entryFactory; + + /** Measures time in a testable way. */ + final Ticker ticker; + + /** + * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. + */ + MapMakerInternalMap(MapMaker builder) { + concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + + keyStrength = builder.getKeyStrength(); + valueStrength = builder.getValueStrength(); + + keyEquivalence = builder.getKeyEquivalence(); + valueEquivalence = valueStrength.defaultEquivalence(); + + maximumSize = builder.maximumSize; + expireAfterAccessNanos = builder.getExpireAfterAccessNanos(); + expireAfterWriteNanos = builder.getExpireAfterWriteNanos(); + + entryFactory = EntryFactory.getFactory(keyStrength, expires(), evictsBySize()); + ticker = builder.getTicker(); + + removalListener = builder.getRemovalListener(); + removalNotificationQueue = (removalListener == NullListener.INSTANCE) + ? MapMakerInternalMap.>discardingQueue() + : new ConcurrentLinkedQueue>(); + + int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + if (evictsBySize()) { + initialCapacity = Math.min(initialCapacity, maximumSize); + } + + // Find power-of-two sizes best matching arguments. Constraints: + // (segmentCount <= maximumSize) + // && (concurrencyLevel > maximumSize || segmentCount > concurrencyLevel) + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel + && (!evictsBySize() || segmentCount * 2 <= maximumSize)) { + ++segmentShift; + segmentCount <<= 1; + } + this.segmentShift = 32 - segmentShift; + segmentMask = segmentCount - 1; + + this.segments = newSegmentArray(segmentCount); + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + + if (evictsBySize()) { + // Ensure sum of segment max sizes = overall max size + int maximumSegmentSize = maximumSize / segmentCount + 1; + int remainder = maximumSize % segmentCount; + for (int i = 0; i < this.segments.length; ++i) { + if (i == remainder) { + maximumSegmentSize--; + } + this.segments[i] = + createSegment(segmentSize, maximumSegmentSize); + } + } else { + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = + createSegment(segmentSize, MapMaker.UNSET_INT); + } + } + } + + boolean evictsBySize() { + return maximumSize != MapMaker.UNSET_INT; + } + + boolean expires() { + return expiresAfterWrite() || expiresAfterAccess(); + } + + boolean expiresAfterWrite() { + return expireAfterWriteNanos > 0; + } + + boolean expiresAfterAccess() { + return expireAfterAccessNanos > 0; + } + + boolean usesKeyReferences() { + return keyStrength != Strength.STRONG; + } + + boolean usesValueReferences() { + return valueStrength != Strength.STRONG; + } + + enum Strength { + /* + * TODO(kevinb): If we strongly reference the value and aren't computing, we needn't wrap the + * value. This could save ~8 bytes per entry. + */ + + STRONG { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value) { + return new StrongValueReference(value); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.equals(); + } + }, + + SOFT { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value) { + return new SoftValueReference(segment.valueReferenceQueue, value, entry); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }, + + WEAK { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value) { + return new WeakValueReference(segment.valueReferenceQueue, value, entry); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }; + + /** + * Creates a reference for the given value according to this value strength. + */ + abstract ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value); + + /** + * Returns the default equivalence strategy used to compare and hash keys or values referenced + * at this strength. This strategy will be used unless the user explicitly specifies an + * alternate strategy. + */ + abstract Equivalence defaultEquivalence(); + } + + /** + * Creates new entries. + */ + enum EntryFactory { + STRONG { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongEntry(key, hash, next); + } + }, + STRONG_EXPIRABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongExpirableEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + return newEntry; + } + }, + STRONG_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongEvictableEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }, + STRONG_EXPIRABLE_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new StrongExpirableEvictableEntry(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }, + + SOFT { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new SoftEntry(segment.keyReferenceQueue, key, hash, next); + } + }, + SOFT_EXPIRABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new SoftExpirableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + return newEntry; + } + }, + SOFT_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new SoftEvictableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }, + SOFT_EXPIRABLE_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new SoftExpirableEvictableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }, + + WEAK { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakEntry(segment.keyReferenceQueue, key, hash, next); + } + }, + WEAK_EXPIRABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakExpirableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + return newEntry; + } + }, + WEAK_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakEvictableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }, + WEAK_EXPIRABLE_EVICTABLE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { + return new WeakExpirableEvictableEntry(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyExpirableEntry(original, newEntry); + copyEvictableEntry(original, newEntry); + return newEntry; + } + }; + + /** + * Masks used to compute indices in the following table. + */ + static final int EXPIRABLE_MASK = 1; + static final int EVICTABLE_MASK = 2; + + /** + * Look-up table for factories. First dimension is the reference type. The second dimension is + * the result of OR-ing the feature masks. + */ + static final EntryFactory[][] factories = { + { STRONG, STRONG_EXPIRABLE, STRONG_EVICTABLE, STRONG_EXPIRABLE_EVICTABLE }, + { SOFT, SOFT_EXPIRABLE, SOFT_EVICTABLE, SOFT_EXPIRABLE_EVICTABLE }, + { WEAK, WEAK_EXPIRABLE, WEAK_EVICTABLE, WEAK_EXPIRABLE_EVICTABLE } + }; + + static EntryFactory getFactory(Strength keyStrength, boolean expireAfterWrite, + boolean evictsBySize) { + int flags = (expireAfterWrite ? EXPIRABLE_MASK : 0) | (evictsBySize ? EVICTABLE_MASK : 0); + return factories[keyStrength.ordinal()][flags]; + } + + /** + * Creates a new entry. + * + * @param segment to create the entry for + * @param key of the entry + * @param hash of the key + * @param next entry in the same bucket + */ + abstract ReferenceEntry newEntry( + Segment segment, K key, int hash, @Nullable ReferenceEntry next); + + /** + * Copies an entry, assigning it a new {@code next} entry. + * + * @param original the entry to copy + * @param newNext entry in the same bucket + */ + @GuardedBy("Segment.this") + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + return newEntry(segment, original.getKey(), original.getHash(), newNext); + } + + @GuardedBy("Segment.this") + void copyExpirableEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectExpirables, nullifyExpirable. + newEntry.setExpirationTime(original.getExpirationTime()); + + connectExpirables(original.getPreviousExpirable(), newEntry); + connectExpirables(newEntry, original.getNextExpirable()); + + nullifyExpirable(original); + } + + @GuardedBy("Segment.this") + void copyEvictableEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectEvictables, nullifyEvictable. + connectEvictables(original.getPreviousEvictable(), newEntry); + connectEvictables(newEntry, original.getNextEvictable()); + + nullifyEvictable(original); + } + } + + /** + * A reference to a value. + */ + interface ValueReference { + /** + * Gets the value. Does not block or throw exceptions. + */ + V get(); + + /** + * Waits for a value that may still be computing. Unlike get(), this method can block (in the + * case of FutureValueReference). + * + * @throws ExecutionException if the computing thread throws an exception + */ + V waitForValue() throws ExecutionException; + + /** + * Returns the entry associated with this value reference, or {@code null} if this value + * reference is independent of any entry. + */ + ReferenceEntry getEntry(); + + /** + * Creates a copy of this reference for the given entry. + * + *

{@code value} may be null only for a loading reference. + */ + ValueReference copyFor( + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry); + + /** + * Clears this reference object. + * + * @param newValue the new value reference which will replace this one; this is only used during + * computation to immediately notify blocked threads of the new value + */ + void clear(@Nullable ValueReference newValue); + + /** + * Returns {@code true} if the value type is a computing reference (regardless of whether or not + * computation has completed). This is necessary to distiguish between partially-collected + * entries and computing entries, which need to be cleaned up differently. + */ + boolean isComputingReference(); + } + + /** + * Placeholder. Indicates that the value hasn't been set yet. + */ + static final ValueReference UNSET = new ValueReference() { + @Override + public Object get() { + return null; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor(ReferenceQueue queue, + @Nullable Object value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public Object waitForValue() { + return null; + } + + @Override + public void clear(ValueReference newValue) {} + }; + + /** + * Singleton placeholder that indicates a value is being computed. + */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ValueReference unset() { + return (ValueReference) UNSET; + } + + /** + * An entry in a reference map. + * + * Entries in the map can be in the following states: + * + * Valid: + * - Live: valid key/value are set + * - Computing: computation is pending + * + * Invalid: + * - Expired: time expired (key/value may still be set) + * - Collected: key/value was partially collected, but not yet cleaned up + */ + interface ReferenceEntry { + /** + * Gets the value reference from this entry. + */ + ValueReference getValueReference(); + + /** + * Sets the value reference for this entry. + */ + void setValueReference(ValueReference valueReference); + + /** + * Gets the next entry in the chain. + */ + ReferenceEntry getNext(); + + /** + * Gets the entry's hash. + */ + int getHash(); + + /** + * Gets the key for this entry. + */ + K getKey(); + + /* + * Used by entries that are expirable. Expirable entries are maintained in a doubly-linked list. + * New entries are added at the tail of the list at write time; stale entries are expired from + * the head of the list. + */ + + /** + * Gets the entry expiration time in ns. + */ + long getExpirationTime(); + + /** + * Sets the entry expiration time in ns. + */ + void setExpirationTime(long time); + + /** + * Gets the next entry in the recency list. + */ + ReferenceEntry getNextExpirable(); + + /** + * Sets the next entry in the recency list. + */ + void setNextExpirable(ReferenceEntry next); + + /** + * Gets the previous entry in the recency list. + */ + ReferenceEntry getPreviousExpirable(); + + /** + * Sets the previous entry in the recency list. + */ + void setPreviousExpirable(ReferenceEntry previous); + + /* + * Implemented by entries that are evictable. Evictable entries are maintained in a + * doubly-linked list. New entries are added at the tail of the list at write time and stale + * entries are expired from the head of the list. + */ + + /** + * Gets the next entry in the recency list. + */ + ReferenceEntry getNextEvictable(); + + /** + * Sets the next entry in the recency list. + */ + void setNextEvictable(ReferenceEntry next); + + /** + * Gets the previous entry in the recency list. + */ + ReferenceEntry getPreviousEvictable(); + + /** + * Sets the previous entry in the recency list. + */ + void setPreviousEvictable(ReferenceEntry previous); + } + + private enum NullEntry implements ReferenceEntry { + INSTANCE; + + @Override + public ValueReference getValueReference() { + return null; + } + + @Override + public void setValueReference(ValueReference valueReference) {} + + @Override + public ReferenceEntry getNext() { + return null; + } + + @Override + public int getHash() { + return 0; + } + + @Override + public Object getKey() { + return null; + } + + @Override + public long getExpirationTime() { + return 0; + } + + @Override + public void setExpirationTime(long time) {} + + @Override + public ReferenceEntry getNextExpirable() { + return this; + } + + @Override + public void setNextExpirable(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousExpirable() { + return this; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) {} + + @Override + public ReferenceEntry getNextEvictable() { + return this; + } + + @Override + public void setNextEvictable(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousEvictable() { + return this; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) {} + } + + abstract static class AbstractReferenceEntry implements ReferenceEntry { + @Override + public ValueReference getValueReference() { + throw new UnsupportedOperationException(); + } + + @Override + public void setValueReference(ValueReference valueReference) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNext() { + throw new UnsupportedOperationException(); + } + + @Override + public int getHash() { + throw new UnsupportedOperationException(); + } + + @Override + public K getKey() { + throw new UnsupportedOperationException(); + } + + @Override + public long getExpirationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExpirationTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + } + + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ReferenceEntry nullEntry() { + return (ReferenceEntry) NullEntry.INSTANCE; + } + + static final Queue DISCARDING_QUEUE = new AbstractQueue() { + @Override + public boolean offer(Object o) { + return true; + } + + @Override + public Object peek() { + return null; + } + + @Override + public Object poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return Iterators.emptyIterator(); + } + }; + + /** + * Queue that discards all elements. + */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static Queue discardingQueue() { + return (Queue) DISCARDING_QUEUE; + } + + /* + * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins! + * To maintain this code, make a change for the strong reference type. Then, cut and paste, and + * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that + * strong entries store the key reference directly while soft and weak entries delegate to their + * respective superclasses. + */ + + /** + * Used for strongly-referenced keys. + */ + static class StrongEntry implements ReferenceEntry { + final K key; + + StrongEntry(K key, int hash, @Nullable ReferenceEntry next) { + this.key = key; + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return this.key; + } + + // null expiration + + @Override + public long getExpirationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExpirationTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null eviction + + @Override + public ReferenceEntry getNextEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + ValueReference previous = this.valueReference; + this.valueReference = valueReference; + previous.clear(valueReference); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class StrongExpirableEntry extends StrongEntry + implements ReferenceEntry { + StrongExpirableEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + } + + static final class StrongEvictableEntry + extends StrongEntry implements ReferenceEntry { + StrongEvictableEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + static final class StrongExpirableEvictableEntry + extends StrongEntry implements ReferenceEntry { + StrongExpirableEvictableEntry(K key, int hash, @Nullable ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + /** + * Used for softly-referenced keys. + */ + static class SoftEntry extends SoftReference implements ReferenceEntry { + SoftEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return get(); + } + + // null expiration + @Override + public long getExpirationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExpirationTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null eviction + + @Override + public ReferenceEntry getNextEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + ValueReference previous = this.valueReference; + this.valueReference = valueReference; + previous.clear(valueReference); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class SoftExpirableEntry + extends SoftEntry implements ReferenceEntry { + SoftExpirableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + } + + static final class SoftEvictableEntry + extends SoftEntry implements ReferenceEntry { + SoftEvictableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + static final class SoftExpirableEvictableEntry + extends SoftEntry implements ReferenceEntry { + SoftExpirableEvictableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + /** + * Used for weakly-referenced keys. + */ + static class WeakEntry extends WeakReference implements ReferenceEntry { + WeakEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return get(); + } + + // null expiration + + @Override + public long getExpirationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExpirationTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousExpirable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null eviction + + @Override + public ReferenceEntry getNextEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousEvictable() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + ValueReference previous = this.valueReference; + this.valueReference = valueReference; + previous.clear(valueReference); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class WeakExpirableEntry + extends WeakEntry implements ReferenceEntry { + WeakExpirableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + } + + static final class WeakEvictableEntry + extends WeakEntry implements ReferenceEntry { + WeakEvictableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + static final class WeakExpirableEvictableEntry + extends WeakEntry implements ReferenceEntry { + WeakExpirableEvictableEntry( + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each expirable entry type. + + volatile long time = Long.MAX_VALUE; + + @Override + public long getExpirationTime() { + return time; + } + + @Override + public void setExpirationTime(long time) { + this.time = time; + } + + @GuardedBy("Segment.this") + ReferenceEntry nextExpirable = nullEntry(); + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousExpirable = nullEntry(); + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + + // The code below is exactly the same for each evictable entry type. + + @GuardedBy("Segment.this") + ReferenceEntry nextEvictable = nullEntry(); + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + @GuardedBy("Segment.this") + ReferenceEntry previousEvictable = nullEntry(); + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + } + + /** + * References a weak value. + */ + static final class WeakValueReference + extends WeakReference implements ValueReference { + final ReferenceEntry entry; + + WeakValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void clear(ValueReference newValue) { + clear(); + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeakValueReference(queue, value, entry); + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** + * References a soft value. + */ + static final class SoftValueReference + extends SoftReference implements ValueReference { + final ReferenceEntry entry; + + SoftValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void clear(ValueReference newValue) { + clear(); + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new SoftValueReference(queue, value, entry); + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** + * References a strong value. + */ + static final class StrongValueReference implements ValueReference { + final V referent; + + StrongValueReference(V referent) { + this.referent = referent; + } + + @Override + public V get() { + return referent; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isComputingReference() { + return false; + } + + @Override + public V waitForValue() { + return get(); + } + + @Override + public void clear(ValueReference newValue) {} + } + + /** + * Applies a supplemental hash function to a given hash code, which defends against poor quality + * hash functions. This is critical when the concurrent hash map uses power-of-two length hash + * tables, that otherwise encounter collisions for hash codes that do not differ in lower or + * upper bits. + * + * @param h hash code + */ + static int rehash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + // TODO(kevinb): use Hashing/move this to Hashing? + h += (h << 15) ^ 0xffffcd7d; + h ^= (h >>> 10); + h += (h << 3); + h ^= (h >>> 6); + h += (h << 2) + (h << 14); + return h ^ (h >>> 16); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { + return segmentFor(hash).newEntry(key, hash, next); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + int hash = original.getHash(); + return segmentFor(hash).copyEntry(original, newNext); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#setValue} instead. + */ + @GuardedBy("Segment.this") + @VisibleForTesting + ValueReference newValueReference(ReferenceEntry entry, V value) { + int hash = entry.getHash(); + return valueStrength.referenceValue(segmentFor(hash), entry, value); + } + + int hash(Object key) { + int h = keyEquivalence.hash(key); + return rehash(h); + } + + void reclaimValue(ValueReference valueReference) { + ReferenceEntry entry = valueReference.getEntry(); + int hash = entry.getHash(); + segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference); + } + + void reclaimKey(ReferenceEntry entry) { + int hash = entry.getHash(); + segmentFor(hash).reclaimKey(entry, hash); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#getLiveValue} + * instead. + */ + @VisibleForTesting + boolean isLive(ReferenceEntry entry) { + return segmentFor(entry.getHash()).getLiveValue(entry) != null; + } + + /** + * Returns the segment that should be used for a key with the given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + // TODO(fry): Lazily create segments? + return segments[(hash >>> segmentShift) & segmentMask]; + } + + Segment createSegment(int initialCapacity, int maxSegmentSize) { + return new Segment(this, initialCapacity, maxSegmentSize); + } + + /** + * Gets the value from an entry. Returns {@code null} if the entry is invalid, + * partially-collected, computing, or expired. Unlike {@link Segment#getLiveValue} this method + * does not attempt to clean up stale entries. + */ + V getLiveValue(ReferenceEntry entry) { + if (entry.getKey() == null) { + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + return null; + } + + if (expires() && isExpired(entry)) { + return null; + } + return value; + } + + // expiration + + /** + * Returns {@code true} if the entry has expired. + */ + boolean isExpired(ReferenceEntry entry) { + return isExpired(entry, ticker.read()); + } + + /** + * Returns {@code true} if the entry has expired. + */ + boolean isExpired(ReferenceEntry entry, long now) { + // if the expiration time had overflowed, this "undoes" the overflow + return now - entry.getExpirationTime() > 0; + } + + @GuardedBy("Segment.this") + static void connectExpirables(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextExpirable(next); + next.setPreviousExpirable(previous); + } + + @GuardedBy("Segment.this") + static void nullifyExpirable(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextExpirable(nullEntry); + nulled.setPreviousExpirable(nullEntry); + } + + // eviction + + /** + * Notifies listeners that an entry has been automatically removed due to expiration, eviction, + * or eligibility for garbage collection. This should be called every time expireEntries or + * evictEntry is called (once the lock is released). + */ + void processPendingNotifications() { + RemovalNotification notification; + while ((notification = removalNotificationQueue.poll()) != null) { + try { + removalListener.onRemoval(notification); + } catch (Exception e) { + logger.log(Level.WARNING, "Exception thrown by removal listener", e); + } + } + } + + /** Links the evitables together. */ + @GuardedBy("Segment.this") + static void connectEvictables(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextEvictable(next); + next.setPreviousEvictable(previous); + } + + @GuardedBy("Segment.this") + static void nullifyEvictable(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextEvictable(nullEntry); + nulled.setPreviousEvictable(nullEntry); + } + + @SuppressWarnings("unchecked") + final Segment[] newSegmentArray(int ssize) { + return new Segment[ssize]; + } + + // Inner Classes + + /** + * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock + * opportunistically, just to simplify some locking and avoid separate construction. + */ + @SuppressWarnings("serial") // This class is never serialized. + static class Segment extends ReentrantLock { + + /* + * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class. + * It will require more memory but will reduce indirection. + */ + + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can + * be read without locking. Next fields of nodes are immutable (final). All list additions are + * performed at the front of each bin. This makes it easy to check changes, and also fast to + * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This + * works well for hash tables since the bin lists tend to be short. (The average length is less + * than two.) + * + * Read operations can thus proceed without locking, but rely on selected uses of volatiles to + * ensure that completed write operations performed by other threads are noticed. For most + * purposes, the "count" field, tracking the number of elements, serves as that volatile + * variable ensuring visibility. This is convenient because this field needs to be read in many + * read operations anyway: + * + * - All (unsynchronized) read operations must first read the "count" field, and should not + * look at table entries if it is 0. + * + * - All (synchronized) write operations should write to the "count" field after structurally + * changing any bin. The operations must not take any action that could even momentarily + * cause a concurrent read operation to see inconsistent data. This is made easier by the + * nature of the read operations in Map. For example, no operation can reveal that the table + * has grown but the threshold has not yet been updated, so there are no atomicity requirements + * for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field are marked in code + * comments. + */ + + final MapMakerInternalMap map; + + /** + * The number of live elements in this segment's region. This does not include unset elements + * which are awaiting cleanup. + */ + volatile int count; + + /** + * Number of updates that alter the size of the table. This is used during bulk-read methods to + * make sure they see a consistent snapshot: If modCounts change during a traversal of segments + * computing size or checking containsValue, then we might have an inconsistent view of state + * so (usually) must retry. + */ + int modCount; + + /** + * The table is expanded when its size exceeds this threshold. (The value of this field is + * always {@code (int)(capacity * 0.75)}.) + */ + int threshold; + + /** + * The per-segment table. + */ + volatile AtomicReferenceArray> table; + + /** + * The maximum size of this map. MapMaker.UNSET_INT if there is no maximum. + */ + final int maxSegmentSize; + + /** + * The key reference queue contains entries whose keys have been garbage collected, and which + * need to be cleaned up internally. + */ + final ReferenceQueue keyReferenceQueue; + + /** + * The value reference queue contains value references whose values have been garbage collected, + * and which need to be cleaned up internally. + */ + final ReferenceQueue valueReferenceQueue; + + /** + * The recency queue is used to record which entries were accessed for updating the eviction + * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is + * crossed or a write occurs on the segment. + */ + final Queue> recencyQueue; + + /** + * A counter of the number of reads since the last write, used to drain queues on a small + * fraction of read operations. + */ + final AtomicInteger readCount = new AtomicInteger(); + + /** + * A queue of elements currently in the map, ordered by access time. Elements are added to the + * tail of the queue on access/write. + */ + @GuardedBy("Segment.this") + final Queue> evictionQueue; + + /** + * A queue of elements currently in the map, ordered by expiration time (either access or write + * time). Elements are added to the tail of the queue on access/write. + */ + @GuardedBy("Segment.this") + final Queue> expirationQueue; + + Segment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize) { + this.map = map; + this.maxSegmentSize = maxSegmentSize; + initTable(newEntryArray(initialCapacity)); + + keyReferenceQueue = map.usesKeyReferences() + ? new ReferenceQueue() : null; + + valueReferenceQueue = map.usesValueReferences() + ? new ReferenceQueue() : null; + + recencyQueue = (map.evictsBySize() || map.expiresAfterAccess()) + ? new ConcurrentLinkedQueue>() + : MapMakerInternalMap.>discardingQueue(); + + evictionQueue = map.evictsBySize() + ? new EvictionQueue() + : MapMakerInternalMap.>discardingQueue(); + + expirationQueue = map.expires() + ? new ExpirationQueue() + : MapMakerInternalMap.>discardingQueue(); + } + + AtomicReferenceArray> newEntryArray(int size) { + return new AtomicReferenceArray>(size); + } + + void initTable(AtomicReferenceArray> newTable) { + this.threshold = newTable.length() * 3 / 4; // 0.75 + if (this.threshold == maxSegmentSize) { + // prevent spurious expansion before eviction + this.threshold++; + } + this.table = newTable; + } + + @GuardedBy("Segment.this") + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { + return map.entryFactory.newEntry(this, key, hash, next); + } + + /** + * Copies {@code original} into a new entry chained to {@code newNext}. Returns the new entry, + * or {@code null} if {@code original} was already garbage collected. + */ + @GuardedBy("Segment.this") + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + if (original.getKey() == null) { + // key collected + return null; + } + + ValueReference valueReference = original.getValueReference(); + V value = valueReference.get(); + if ((value == null) && !valueReference.isComputingReference()) { + // value collected + return null; + } + + ReferenceEntry newEntry = map.entryFactory.copyEntry(this, original, newNext); + newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry)); + return newEntry; + } + + /** + * Sets a new value of an entry. Adds newly created entries at the end of the expiration queue. + */ + @GuardedBy("Segment.this") + void setValue(ReferenceEntry entry, V value) { + ValueReference valueReference = map.valueStrength.referenceValue(this, entry, value); + entry.setValueReference(valueReference); + recordWrite(entry); + } + + // reference queues, for garbage collection cleanup + + /** + * Cleanup collected entries when the lock is available. + */ + void tryDrainReferenceQueues() { + if (tryLock()) { + try { + drainReferenceQueues(); + } finally { + unlock(); + } + } + } + + /** + * Drain the key and value reference queues, cleaning up internal entries containing garbage + * collected keys or values. + */ + @GuardedBy("Segment.this") + void drainReferenceQueues() { + if (map.usesKeyReferences()) { + drainKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + drainValueReferenceQueue(); + } + } + + @GuardedBy("Segment.this") + void drainKeyReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = keyReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ReferenceEntry entry = (ReferenceEntry) ref; + map.reclaimKey(entry); + if (++i == DRAIN_MAX) { + break; + } + } + } + + @GuardedBy("Segment.this") + void drainValueReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = valueReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ValueReference valueReference = (ValueReference) ref; + map.reclaimValue(valueReference); + if (++i == DRAIN_MAX) { + break; + } + } + } + + /** + * Clears all entries from the key and value reference queues. + */ + void clearReferenceQueues() { + if (map.usesKeyReferences()) { + clearKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + clearValueReferenceQueue(); + } + } + + void clearKeyReferenceQueue() { + while (keyReferenceQueue.poll() != null) {} + } + + void clearValueReferenceQueue() { + while (valueReferenceQueue.poll() != null) {} + } + + // recency queue, shared by expiration and eviction + + /** + * Records the relative order in which this read was performed by adding {@code entry} to the + * recency queue. At write-time, or when the queue is full past the threshold, the queue will + * be drained and the entries therein processed. + * + *

Note: locked reads should use {@link #recordLockedRead}. + */ + void recordRead(ReferenceEntry entry) { + if (map.expiresAfterAccess()) { + recordExpirationTime(entry, map.expireAfterAccessNanos); + } + recencyQueue.add(entry); + } + + /** + * Updates the eviction metadata that {@code entry} was just read. This currently amounts to + * adding {@code entry} to relevant eviction lists. + * + *

Note: this method should only be called under lock, as it directly manipulates the + * eviction queues. Unlocked reads should use {@link #recordRead}. + */ + @GuardedBy("Segment.this") + void recordLockedRead(ReferenceEntry entry) { + evictionQueue.add(entry); + if (map.expiresAfterAccess()) { + recordExpirationTime(entry, map.expireAfterAccessNanos); + expirationQueue.add(entry); + } + } + + /** + * Updates eviction metadata that {@code entry} was just written. This currently amounts to + * adding {@code entry} to relevant eviction lists. + */ + @GuardedBy("Segment.this") + void recordWrite(ReferenceEntry entry) { + // we are already under lock, so drain the recency queue immediately + drainRecencyQueue(); + evictionQueue.add(entry); + if (map.expires()) { + // currently MapMaker ensures that expireAfterWrite and + // expireAfterAccess are mutually exclusive + long expiration = map.expiresAfterAccess() + ? map.expireAfterAccessNanos + : map.expireAfterWriteNanos; + recordExpirationTime(entry, expiration); + expirationQueue.add(entry); + } + } + + /** + * Drains the recency queue, updating eviction metadata that the entries therein were read in + * the specified relative order. This currently amounts to adding them to relevant eviction + * lists (accounting for the fact that they could have been removed from the map since being + * added to the recency queue). + */ + @GuardedBy("Segment.this") + void drainRecencyQueue() { + ReferenceEntry e; + while ((e = recencyQueue.poll()) != null) { + // An entry may be in the recency queue despite it being removed from + // the map . This can occur when the entry was concurrently read while a + // writer is removing it from the segment or after a clear has removed + // all of the segment's entries. + if (evictionQueue.contains(e)) { + evictionQueue.add(e); + } + if (map.expiresAfterAccess() && expirationQueue.contains(e)) { + expirationQueue.add(e); + } + } + } + + // expiration + + void recordExpirationTime(ReferenceEntry entry, long expirationNanos) { + // might overflow, but that's okay (see isExpired()) + entry.setExpirationTime(map.ticker.read() + expirationNanos); + } + + /** + * Cleanup expired entries when the lock is available. + */ + void tryExpireEntries() { + if (tryLock()) { + try { + expireEntries(); + } finally { + unlock(); + // don't call postWriteCleanup as we're in a read + } + } + } + + @GuardedBy("Segment.this") + void expireEntries() { + drainRecencyQueue(); + + if (expirationQueue.isEmpty()) { + // There's no point in calling nanoTime() if we have no entries to + // expire. + return; + } + long now = map.ticker.read(); + ReferenceEntry e; + while ((e = expirationQueue.peek()) != null && map.isExpired(e, now)) { + if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { + throw new AssertionError(); + } + } + } + + // eviction + + void enqueueNotification(ReferenceEntry entry, RemovalCause cause) { + enqueueNotification(entry.getKey(), entry.getHash(), entry.getValueReference().get(), cause); + } + + void enqueueNotification(@Nullable K key, int hash, @Nullable V value, RemovalCause cause) { + if (map.removalNotificationQueue != DISCARDING_QUEUE) { + RemovalNotification notification = new RemovalNotification(key, value, cause); + map.removalNotificationQueue.offer(notification); + } + } + + /** + * Performs eviction if the segment is full. This should only be called prior to adding a new + * entry and increasing {@code count}. + * + * @return {@code true} if eviction occurred + */ + @GuardedBy("Segment.this") + boolean evictEntries() { + if (map.evictsBySize() && count >= maxSegmentSize) { + drainRecencyQueue(); + + ReferenceEntry e = evictionQueue.remove(); + if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) { + throw new AssertionError(); + } + return true; + } + return false; + } + + /** + * Returns first entry of bin for given hash. + */ + ReferenceEntry getFirst(int hash) { + // read this volatile field only once + AtomicReferenceArray> table = this.table; + return table.get(hash & (table.length() - 1)); + } + + // Specialized implementations of map methods + + ReferenceEntry getEntry(Object key, int hash) { + if (count != 0) { // read-volatile + for (ReferenceEntry e = getFirst(hash); e != null; e = e.getNext()) { + if (e.getHash() != hash) { + continue; + } + + K entryKey = e.getKey(); + if (entryKey == null) { + tryDrainReferenceQueues(); + continue; + } + + if (map.keyEquivalence.equivalent(key, entryKey)) { + return e; + } + } + } + + return null; + } + + ReferenceEntry getLiveEntry(Object key, int hash) { + ReferenceEntry e = getEntry(key, hash); + if (e == null) { + return null; + } else if (map.expires() && map.isExpired(e)) { + tryExpireEntries(); + return null; + } + return e; + } + + V get(Object key, int hash) { + try { + ReferenceEntry e = getLiveEntry(key, hash); + if (e == null) { + return null; + } + + V value = e.getValueReference().get(); + if (value != null) { + recordRead(e); + } else { + tryDrainReferenceQueues(); + } + return value; + } finally { + postReadCleanup(); + } + } + + boolean containsKey(Object key, int hash) { + try { + if (count != 0) { // read-volatile + ReferenceEntry e = getLiveEntry(key, hash); + if (e == null) { + return false; + } + return e.getValueReference().get() != null; + } + + return false; + } finally { + postReadCleanup(); + } + } + + /** + * This method is a convenience for testing. Code should call {@link + * MapMakerInternalMap#containsValue} directly. + */ + @VisibleForTesting + boolean containsValue(Object value) { + try { + if (count != 0) { // read-volatile + AtomicReferenceArray> table = this.table; + int length = table.length(); + for (int i = 0; i < length; ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + V entryValue = getLiveValue(e); + if (entryValue == null) { + continue; + } + if (map.valueEquivalence.equivalent(value, entryValue)) { + return true; + } + } + } + } + + return false; + } finally { + postReadCleanup(); + } + } + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + // Look for an existing entry. + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + if (entryValue == null) { + ++modCount; + setValue(e, value); + if (!valueReference.isComputingReference()) { + enqueueNotification(key, hash, entryValue, RemovalCause.COLLECTED); + newCount = this.count; // count remains unchanged + } else if (evictEntries()) { // evictEntries after setting new value + newCount = this.count + 1; + } + this.count = newCount; // write-volatile + return null; + } else if (onlyIfAbsent) { + // Mimic + // "if (!map.containsKey(key)) ... + // else return map.get(key); + recordLockedRead(e); + return entryValue; + } else { + // clobber existing entry, count remains unchanged + ++modCount; + enqueueNotification(key, hash, entryValue, RemovalCause.REPLACED); + setValue(e, value); + return entryValue; + } + } + } + + // Create a new entry. + ++modCount; + ReferenceEntry newEntry = newEntry(key, hash, first); + setValue(newEntry, value); + table.set(index, newEntry); + if (evictEntries()) { // evictEntries after setting new value + newCount = this.count + 1; + } + this.count = newCount; // write-volatile + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** + * Expands the table if possible. + */ + @GuardedBy("Segment.this") + void expand() { + AtomicReferenceArray> oldTable = table; + int oldCapacity = oldTable.length(); + if (oldCapacity >= MAXIMUM_CAPACITY) { + return; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move with a power of two offset. + * We eliminate unnecessary node creation by catching cases where old nodes can be reused + * because their next fields won't change. Statistically, at the default threshold, only + * about one-sixth of them need cloning when a table doubles. The nodes they replace will be + * garbage collectable as soon as they are no longer referenced by any reader thread that may + * be in the midst of traversing table right now. + */ + + int newCount = count; + AtomicReferenceArray> newTable = newEntryArray(oldCapacity << 1); + threshold = newTable.length() * 3 / 4; + int newMask = newTable.length() - 1; + for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + ReferenceEntry head = oldTable.get(oldIndex); + + if (head != null) { + ReferenceEntry next = head.getNext(); + int headIndex = head.getHash() & newMask; + + // Single node on list + if (next == null) { + newTable.set(headIndex, head); + } else { + // Reuse the consecutive sequence of nodes with the same target + // index from the end of the list. tail points to the first + // entry in the reusable list. + ReferenceEntry tail = head; + int tailIndex = headIndex; + for (ReferenceEntry e = next; e != null; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + if (newIndex != tailIndex) { + // The index changed. We'll need to copy the previous entry. + tailIndex = newIndex; + tail = e; + } + } + newTable.set(tailIndex, tail); + + // Clone nodes leading up to the tail. + for (ReferenceEntry e = head; e != tail; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + ReferenceEntry newNext = newTable.get(newIndex); + ReferenceEntry newFirst = copyEntry(e, newNext); + if (newFirst != null) { + newTable.set(newIndex, newFirst); + } else { + removeCollectedEntry(e); + newCount--; + } + } + } + } + } + table = newTable; + this.count = newCount; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + preWriteCleanup(); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (isCollected(valueReference)) { + int newCount = this.count - 1; + ++modCount; + enqueueNotification(entryKey, hash, entryValue, RemovalCause.COLLECTED); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return false; + } + + if (map.valueEquivalence.equivalent(oldValue, entryValue)) { + ++modCount; + enqueueNotification(key, hash, entryValue, RemovalCause.REPLACED); + setValue(e, newValue); + return true; + } else { + // Mimic + // "if (map.containsKey(key) && map.get(key).equals(oldValue))..." + recordLockedRead(e); + return false; + } + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + V replace(K key, int hash, V newValue) { + lock(); + try { + preWriteCleanup(); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (isCollected(valueReference)) { + int newCount = this.count - 1; + ++modCount; + enqueueNotification(entryKey, hash, entryValue, RemovalCause.COLLECTED); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return null; + } + + ++modCount; + enqueueNotification(key, hash, entryValue, RemovalCause.REPLACED); + setValue(e, newValue); + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + V remove(Object key, int hash) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (entryValue != null) { + cause = RemovalCause.EXPLICIT; + } else if (isCollected(valueReference)) { + cause = RemovalCause.COLLECTED; + } else { + return null; + } + + ++modCount; + enqueueNotification(entryKey, hash, entryValue, cause); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + boolean remove(Object key, int hash, Object value) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (map.valueEquivalence.equivalent(value, entryValue)) { + cause = RemovalCause.EXPLICIT; + } else if (isCollected(valueReference)) { + cause = RemovalCause.COLLECTED; + } else { + return false; + } + + ++modCount; + enqueueNotification(entryKey, hash, entryValue, cause); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return (cause == RemovalCause.EXPLICIT); + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + void clear() { + if (count != 0) { + lock(); + try { + AtomicReferenceArray> table = this.table; + if (map.removalNotificationQueue != DISCARDING_QUEUE) { + for (int i = 0; i < table.length(); ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + // Computing references aren't actually in the map yet. + if (!e.getValueReference().isComputingReference()) { + enqueueNotification(e, RemovalCause.EXPLICIT); + } + } + } + } + for (int i = 0; i < table.length(); ++i) { + table.set(i, null); + } + clearReferenceQueues(); + evictionQueue.clear(); + expirationQueue.clear(); + readCount.set(0); + + ++modCount; + count = 0; // write-volatile + } finally { + unlock(); + postWriteCleanup(); + } + } + } + + /** + * Removes an entry from within a table. All entries following the removed node can stay, but + * all preceding ones need to be cloned. + * + *

This method does not decrement count for the removed entry, but does decrement count for + * all partially collected entries which are skipped. As such callers which are modifying count + * must re-read it after calling removeFromChain. + * + * @param first the first entry of the table + * @param entry the entry being removed from the table + * @return the new first entry for the table + */ + @GuardedBy("Segment.this") + ReferenceEntry removeFromChain(ReferenceEntry first, ReferenceEntry entry) { + evictionQueue.remove(entry); + expirationQueue.remove(entry); + + int newCount = count; + ReferenceEntry newFirst = entry.getNext(); + for (ReferenceEntry e = first; e != entry; e = e.getNext()) { + ReferenceEntry next = copyEntry(e, newFirst); + if (next != null) { + newFirst = next; + } else { + removeCollectedEntry(e); + newCount--; + } + } + this.count = newCount; + return newFirst; + } + + void removeCollectedEntry(ReferenceEntry entry) { + enqueueNotification(entry, RemovalCause.COLLECTED); + evictionQueue.remove(entry); + expirationQueue.remove(entry); + } + + /** + * Removes an entry whose key has been garbage collected. + */ + boolean reclaimKey(ReferenceEntry entry, int hash) { + lock(); + try { + int newCount = count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + enqueueNotification( + e.getKey(), hash, e.getValueReference().get(), RemovalCause.COLLECTED); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** + * Removes an entry whose value has been garbage collected. + */ + boolean reclaimValue(K key, int hash, ValueReference valueReference) { + lock(); + try { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + ++modCount; + enqueueNotification(key, hash, valueReference.get(), RemovalCause.COLLECTED); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + if (!isHeldByCurrentThread()) { // don't cleanup inside of put + postWriteCleanup(); + } + } + } + + /** + * Clears a value that has not yet been set, and thus does not require count to be modified. + */ + boolean clearValue(K key, int hash, ValueReference valueReference) { + lock(); + try { + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + ReferenceEntry newFirst = removeFromChain(first, e); + table.set(index, newFirst); + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + @GuardedBy("Segment.this") + boolean removeEntry(ReferenceEntry entry, int hash, RemovalCause cause) { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + enqueueNotification(e.getKey(), hash, e.getValueReference().get(), cause); + ReferenceEntry newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } + + /** + * Returns {@code true} if the entry has been partially collected, meaning that either the key + * is null, or the value is null and it is not computing. + */ + boolean isCollected(ReferenceEntry entry) { + if (entry.getKey() == null) { + return true; + } + return isCollected(entry.getValueReference()); + } + + /** + * Returns {@code true} if the value has been partially collected, meaning that the value is + * null and it is not computing. + */ + boolean isCollected(ValueReference valueReference) { + if (valueReference.isComputingReference()) { + return false; + } + return (valueReference.get() == null); + } + + /** + * Gets the value from an entry. Returns {@code null} if the entry is invalid, + * partially-collected, computing, or expired. + */ + V getLiveValue(ReferenceEntry entry) { + if (entry.getKey() == null) { + tryDrainReferenceQueues(); + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + tryDrainReferenceQueues(); + return null; + } + + if (map.expires() && map.isExpired(entry)) { + tryExpireEntries(); + return null; + } + return value; + } + + /** + * Performs routine cleanup following a read. Normally cleanup happens during writes, or from + * the cleanupExecutor. If cleanup is not observed after a sufficient number of reads, try + * cleaning up from the read thread. + */ + void postReadCleanup() { + if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) { + runCleanup(); + } + } + + /** + * Performs routine cleanup prior to executing a write. This should be called every time a + * write thread acquires the segment lock, immediately after acquiring the lock. + * + *

Post-condition: expireEntries has been run. + */ + @GuardedBy("Segment.this") + void preWriteCleanup() { + runLockedCleanup(); + } + + /** + * Performs routine cleanup following a write. + */ + void postWriteCleanup() { + runUnlockedCleanup(); + } + + void runCleanup() { + runLockedCleanup(); + runUnlockedCleanup(); + } + + void runLockedCleanup() { + if (tryLock()) { + try { + drainReferenceQueues(); + expireEntries(); // calls drainRecencyQueue + readCount.set(0); + } finally { + unlock(); + } + } + } + + void runUnlockedCleanup() { + // locked cleanup may generate notifications we can send unlocked + if (!isHeldByCurrentThread()) { + map.processPendingNotifications(); + } + } + + } + + // Queues + + /** + * A custom queue for managing eviction order. Note that this is tightly integrated with {@code + * ReferenceEntry}, upon which it relies to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in + * the map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle + * of the queue as part of copyEvictableEntry, and (2) the contains method is highly optimized + * for the current model. + */ + static final class EvictionQueue extends AbstractQueue> { + final ReferenceEntry head = new AbstractReferenceEntry() { + + ReferenceEntry nextEvictable = this; + + @Override + public ReferenceEntry getNextEvictable() { + return nextEvictable; + } + + @Override + public void setNextEvictable(ReferenceEntry next) { + this.nextEvictable = next; + } + + ReferenceEntry previousEvictable = this; + + @Override + public ReferenceEntry getPreviousEvictable() { + return previousEvictable; + } + + @Override + public void setPreviousEvictable(ReferenceEntry previous) { + this.previousEvictable = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectEvictables(entry.getPreviousEvictable(), entry.getNextEvictable()); + + // add to tail + connectEvictables(head.getPreviousEvictable(), entry); + connectEvictables(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextEvictable(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextEvictable(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousEvictable(); + ReferenceEntry next = e.getNextEvictable(); + connectEvictables(previous, next); + nullifyEvictable(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextEvictable() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextEvictable() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextEvictable(); e != head; e = e.getNextEvictable()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextEvictable(); + while (e != head) { + ReferenceEntry next = e.getNextEvictable(); + nullifyEvictable(e); + e = next; + } + + head.setNextEvictable(head); + head.setPreviousEvictable(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextEvictable(); + return (next == head) ? null : next; + } + }; + } + } + + /** + * A custom queue for managing expiration order. Note that this is tightly integrated with + * {@code ReferenceEntry}, upon which it reliese to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in + * the map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle + * of the queue as part of copyEvictableEntry, and (2) the contains method is highly optimized + * for the current model. + */ + static final class ExpirationQueue extends AbstractQueue> { + final ReferenceEntry head = new AbstractReferenceEntry() { + + @Override + public long getExpirationTime() { + return Long.MAX_VALUE; + } + + @Override + public void setExpirationTime(long time) {} + + ReferenceEntry nextExpirable = this; + + @Override + public ReferenceEntry getNextExpirable() { + return nextExpirable; + } + + @Override + public void setNextExpirable(ReferenceEntry next) { + this.nextExpirable = next; + } + + ReferenceEntry previousExpirable = this; + + @Override + public ReferenceEntry getPreviousExpirable() { + return previousExpirable; + } + + @Override + public void setPreviousExpirable(ReferenceEntry previous) { + this.previousExpirable = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectExpirables(entry.getPreviousExpirable(), entry.getNextExpirable()); + + // add to tail + connectExpirables(head.getPreviousExpirable(), entry); + connectExpirables(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextExpirable(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextExpirable(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousExpirable(); + ReferenceEntry next = e.getNextExpirable(); + connectExpirables(previous, next); + nullifyExpirable(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextExpirable() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextExpirable() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextExpirable(); e != head; e = e.getNextExpirable()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextExpirable(); + while (e != head) { + ReferenceEntry next = e.getNextExpirable(); + nullifyExpirable(e); + e = next; + } + + head.setNextExpirable(head); + head.setPreviousExpirable(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextExpirable(); + return (next == head) ? null : next; + } + }; + } + } + + static final class CleanupMapTask implements Runnable { + final WeakReference> mapReference; + + public CleanupMapTask(MapMakerInternalMap map) { + this.mapReference = new WeakReference>(map); + } + + @Override + public void run() { + MapMakerInternalMap map = mapReference.get(); + if (map == null) { + throw new CancellationException(); + } + + for (Segment segment : map.segments) { + segment.runCleanup(); + } + } + } + + // ConcurrentMap methods + + @Override + public boolean isEmpty() { + /* + * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and + * removed in one segment while checking another, in which case the table was never actually + * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment + * modifications before recheck.) Method containsValue() uses similar constructions for + * stability checks. + */ + long sum = 0L; + Segment[] segments = this.segments; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum += segments[i].modCount; + } + + if (sum != 0L) { // recheck unless no modifications + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum -= segments[i].modCount; + } + if (sum != 0L) { + return false; + } + } + return true; + } + + @Override + public int size() { + Segment[] segments = this.segments; + long sum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + } + return Ints.saturatedCast(sum); + } + + @Override + public V get(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).get(key, hash); + } + + /** + * Returns the internal entry for the specified key. The entry may be computing, expired, or + * partially collected. Does not impact recency ordering. + */ + ReferenceEntry getEntry(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getEntry(key, hash); + } + + /** + * Returns the live internal entry for the specified key. Does not impact recency ordering. + */ + ReferenceEntry getLiveEntry(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getLiveEntry(key, hash); + } + + @Override + public boolean containsKey(@Nullable Object key) { + if (key == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).containsKey(key, hash); + } + + @Override + public boolean containsValue(@Nullable Object value) { + if (value == null) { + return false; + } + + // This implementation is patterned after ConcurrentHashMap, but without the locking. The only + // way for it to return a false negative would be for the target value to jump around in the map + // such that none of the subsequent iterations observed it, despite the fact that at every point + // in time it was present somewhere int the map. This becomes increasingly unlikely as + // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. + final Segment[] segments = this.segments; + long last = -1L; + for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { + long sum = 0L; + for (Segment segment : segments) { + // ensure visibility of most recent completed write + @SuppressWarnings({"UnusedDeclaration", "unused"}) + int c = segment.count; // read-volatile + + AtomicReferenceArray> table = segment.table; + for (int j = 0; j < table.length(); j++) { + for (ReferenceEntry e = table.get(j); e != null; e = e.getNext()) { + V v = segment.getLiveValue(e); + if (v != null && valueEquivalence.equivalent(value, v)) { + return true; + } + } + } + sum += segment.modCount; + } + if (sum == last) { + break; + } + last = sum; + } + return false; + } + + @Override + public V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, false); + } + + @Override + public V putIfAbsent(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, true); + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + @Override + public V remove(@Nullable Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash); + } + + @Override + public boolean remove(@Nullable Object key, @Nullable Object value) { + if (key == null || value == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash, value); + } + + @Override + public boolean replace(K key, @Nullable V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(newValue); + if (oldValue == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).replace(key, hash, value); + } + + @Override + public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + } + + transient Set keySet; + + @Override + public Set keySet() { + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet()); + } + + transient Collection values; + + @Override + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + // Iterator Support + + abstract class HashIterator { + + int nextSegmentIndex; + int nextTableIndex; + Segment currentSegment; + AtomicReferenceArray> currentTable; + ReferenceEntry nextEntry; + WriteThroughEntry nextExternal; + WriteThroughEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + final void advance() { + nextExternal = null; + + if (nextInChain()) { + return; + } + + if (nextInTable()) { + return; + } + + while (nextSegmentIndex >= 0) { + currentSegment = segments[nextSegmentIndex--]; + if (currentSegment.count != 0) { + currentTable = currentSegment.table; + nextTableIndex = currentTable.length() - 1; + if (nextInTable()) { + return; + } + } + } + } + + /** + * Finds the next entry in the current chain. Returns {@code true} if an entry was found. + */ + boolean nextInChain() { + if (nextEntry != null) { + for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) { + if (advanceTo(nextEntry)) { + return true; + } + } + } + return false; + } + + /** + * Finds the next entry in the current table. Returns {@code true} if an entry was found. + */ + boolean nextInTable() { + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { + if (advanceTo(nextEntry) || nextInChain()) { + return true; + } + } + } + return false; + } + + /** + * Advances to the given entry. Returns {@code true} if the entry was valid, {@code false} if it + * should be skipped. + */ + boolean advanceTo(ReferenceEntry entry) { + try { + K key = entry.getKey(); + V value = getLiveValue(entry); + if (value != null) { + nextExternal = new WriteThroughEntry(key, value); + return true; + } else { + // Skip stale entry. + return false; + } + } finally { + currentSegment.postReadCleanup(); + } + } + + public boolean hasNext() { + return nextExternal != null; + } + + WriteThroughEntry nextEntry() { + if (nextExternal == null) { + throw new NoSuchElementException(); + } + lastReturned = nextExternal; + advance(); + return lastReturned; + } + + public void remove() { + checkState(lastReturned != null); + MapMakerInternalMap.this.remove(lastReturned.getKey()); + lastReturned = null; + } + } + + final class KeyIterator extends HashIterator implements Iterator { + + @Override + public K next() { + return nextEntry().getKey(); + } + } + + final class ValueIterator extends HashIterator implements Iterator { + + @Override + public V next() { + return nextEntry().getValue(); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the + * underlying map. + */ + final class WriteThroughEntry extends AbstractMapEntry { + final K key; // non-null + V value; // non-null + + WriteThroughEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(@Nullable Object object) { + // Cannot use key and value equivalence + if (object instanceof Entry) { + Entry that = (Entry) object; + return key.equals(that.getKey()) && value.equals(that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + // Cannot use key and value equivalence + return key.hashCode() ^ value.hashCode(); + } + + @Override + public V setValue(V newValue) { + V oldValue = put(key, newValue); + value = newValue; // only if put succeeds + return oldValue; + } + } + + final class EntryIterator extends HashIterator implements Iterator> { + + @Override + public Entry next() { + return nextEntry(); + } + } + + final class KeySet extends AbstractSet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return MapMakerInternalMap.this.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return MapMakerInternalMap.this.remove(o) != null; + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + } + + final class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return MapMakerInternalMap.this.containsValue(o); + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + if (key == null) { + return false; + } + V v = MapMakerInternalMap.this.get(key); + + return v != null && valueEquivalence.equivalent(e.getValue(), v); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + return key != null && MapMakerInternalMap.this.remove(key, e.getValue()); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + } + + // Serialization Support + + private static final long serialVersionUID = 5; + + Object writeReplace() { + return new SerializationProxy(keyStrength, valueStrength, keyEquivalence, + valueEquivalence, expireAfterWriteNanos, expireAfterAccessNanos, maximumSize, + concurrencyLevel, removalListener, this); + } + + /** + * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a + * circular dependency is present, so the proxy must be able to behave as the map itself. + */ + abstract static class AbstractSerializationProxy + extends ForwardingConcurrentMap implements Serializable { + private static final long serialVersionUID = 3; + + final Strength keyStrength; + final Strength valueStrength; + final Equivalence keyEquivalence; + final Equivalence valueEquivalence; + final long expireAfterWriteNanos; + final long expireAfterAccessNanos; + final int maximumSize; + final int concurrencyLevel; + final RemovalListener removalListener; + + transient ConcurrentMap delegate; + + AbstractSerializationProxy(Strength keyStrength, Strength valueStrength, + Equivalence keyEquivalence, Equivalence valueEquivalence, + long expireAfterWriteNanos, long expireAfterAccessNanos, int maximumSize, + int concurrencyLevel, RemovalListener removalListener, + ConcurrentMap delegate) { + this.keyStrength = keyStrength; + this.valueStrength = valueStrength; + this.keyEquivalence = keyEquivalence; + this.valueEquivalence = valueEquivalence; + this.expireAfterWriteNanos = expireAfterWriteNanos; + this.expireAfterAccessNanos = expireAfterAccessNanos; + this.maximumSize = maximumSize; + this.concurrencyLevel = concurrencyLevel; + this.removalListener = removalListener; + this.delegate = delegate; + } + + @Override + protected ConcurrentMap delegate() { + return delegate; + } + + void writeMapTo(ObjectOutputStream out) throws IOException { + out.writeInt(delegate.size()); + for (Entry entry : delegate.entrySet()) { + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + out.writeObject(null); // terminate entries + } + + @SuppressWarnings("deprecation") // serialization of deprecated feature + MapMaker readMapMaker(ObjectInputStream in) throws IOException { + int size = in.readInt(); + MapMaker mapMaker = new MapMaker() + .initialCapacity(size) + .setKeyStrength(keyStrength) + .setValueStrength(valueStrength) + .keyEquivalence(keyEquivalence) + .concurrencyLevel(concurrencyLevel); + mapMaker.removalListener(removalListener); + if (expireAfterWriteNanos > 0) { + mapMaker.expireAfterWrite(expireAfterWriteNanos, TimeUnit.NANOSECONDS); + } + if (expireAfterAccessNanos > 0) { + mapMaker.expireAfterAccess(expireAfterAccessNanos, TimeUnit.NANOSECONDS); + } + if (maximumSize != MapMaker.UNSET_INT) { + mapMaker.maximumSize(maximumSize); + } + return mapMaker; + } + + @SuppressWarnings("unchecked") + void readEntries(ObjectInputStream in) throws IOException, ClassNotFoundException { + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; // terminator + } + V value = (V) in.readObject(); + delegate.put(key, value); + } + } + } + + /** + * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a + * circular dependency is present, so the proxy must be able to behave as the map itself. + */ + private static final class SerializationProxy extends AbstractSerializationProxy { + private static final long serialVersionUID = 3; + + SerializationProxy(Strength keyStrength, Strength valueStrength, + Equivalence keyEquivalence, Equivalence valueEquivalence, + long expireAfterWriteNanos, long expireAfterAccessNanos, int maximumSize, + int concurrencyLevel, RemovalListener removalListener, + ConcurrentMap delegate) { + super(keyStrength, valueStrength, keyEquivalence, valueEquivalence, expireAfterWriteNanos, + expireAfterAccessNanos, maximumSize, concurrencyLevel, removalListener, delegate); + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + writeMapTo(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + MapMaker mapMaker = readMapMaker(in); + delegate = mapMaker.makeMap(); + readEntries(in); + } + + private Object readResolve() { + return delegate; + } + } +} diff --git a/guava/src/com/google/common/collect/Maps.java b/guava/src/com/google/common/collect/Maps.java new file mode 100644 index 0000000..73978b7 --- /dev/null +++ b/guava/src/com/google/common/collect/Maps.java @@ -0,0 +1,3240 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Joiner.MapJoiner; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.MapDifference.ValueDifference; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentMap; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link Map} instances (including instances of + * {@link SortedMap}, {@link BiMap}, etc.). Also see this class's counterparts + * {@link Lists}, {@link Sets} and {@link Queues}. + * + *

See the Guava User Guide article on + * {@code Maps}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Isaac Shum + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Maps { + private Maps() {} + + /** + * Creates a mutable, empty {@code HashMap} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#of()} instead. + * + *

Note: if {@code K} is an {@code enum} type, use {@link + * #newEnumMap} instead. + * + * @return a new, empty {@code HashMap} + */ + public static HashMap newHashMap() { + return new HashMap(); + } + + /** + * Creates a {@code HashMap} instance, with a high enough "initial capacity" + * that it should hold {@code expectedSize} elements without growth. + * This behavior cannot be broadly guaranteed, but it is observed to be true + * for OpenJDK 1.6. It also can't be guaranteed that the method isn't + * inadvertently oversizing the returned map. + * + * @param expectedSize the number of elements you expect to add to the + * returned map + * @return a new, empty {@code HashMap} with enough capacity to hold {@code + * expectedSize} elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize( + int expectedSize) { + return new HashMap(capacity(expectedSize)); + } + + /** + * Returns a capacity that is sufficient to keep the map from being resized as + * long as it grows no larger than expectedSize and the load factor is >= its + * default (0.75). + */ + static int capacity(int expectedSize) { + if (expectedSize < 3) { + checkArgument(expectedSize >= 0); + return expectedSize + 1; + } + if (expectedSize < Ints.MAX_POWER_OF_TWO) { + return expectedSize + expectedSize / 3; + } + return Integer.MAX_VALUE; // any large value + } + + /** + * Creates a mutable {@code HashMap} instance with the same mappings as + * the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#copyOf(Map)} instead. + * + *

Note: if {@code K} is an {@link Enum} type, use {@link + * #newEnumMap} instead. + * + * @param map the mappings to be placed in the new map + * @return a new {@code HashMap} initialized with the mappings from {@code + * map} + */ + public static HashMap newHashMap( + Map map) { + return new HashMap(map); + } + + /** + * Creates a mutable, empty, insertion-ordered {@code LinkedHashMap} + * instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#of()} instead. + * + * @return a new, empty {@code LinkedHashMap} + */ + public static LinkedHashMap newLinkedHashMap() { + return new LinkedHashMap(); + } + + /** + * Creates a mutable, insertion-ordered {@code LinkedHashMap} instance + * with the same mappings as the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#copyOf(Map)} instead. + * + * @param map the mappings to be placed in the new map + * @return a new, {@code LinkedHashMap} initialized with the mappings from + * {@code map} + */ + public static LinkedHashMap newLinkedHashMap( + Map map) { + return new LinkedHashMap(map); + } + + /** + * Returns a general-purpose instance of {@code ConcurrentMap}, which supports + * all optional operations of the ConcurrentMap interface. It does not permit + * null keys or values. It is serializable. + * + *

This is currently accomplished by calling {@link MapMaker#makeMap()}. + * + *

It is preferable to use {@code MapMaker} directly (rather than through + * this method), as it presents numerous useful configuration options, + * such as the concurrency level, load factor, key/value reference types, + * and value computation. + * + * @return a new, empty {@code ConcurrentMap} + * @since 3.0 + */ + public static ConcurrentMap newConcurrentMap() { + return new MapMaker().makeMap(); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the natural + * ordering of its elements. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedMap#of()} instead. + * + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap() { + return new TreeMap(); + } + + /** + * Creates a mutable {@code TreeMap} instance with the same mappings as + * the specified map and using the same ordering as the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedMap#copyOfSorted(SortedMap)} instead. + * + * @param map the sorted map whose mappings are to be placed in the new map + * and whose comparator is to be used to sort the new map + * @return a new {@code TreeMap} initialized with the mappings from {@code + * map} and using the comparator of {@code map} + */ + public static TreeMap newTreeMap(SortedMap map) { + return new TreeMap(map); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the given + * comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedMap.orderedBy(comparator).build()} instead. + * + * @param comparator the comparator to sort the keys with + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap( + @Nullable Comparator comparator) { + // Ideally, the extra type parameter "C" shouldn't be necessary. It is a + // work-around of a compiler type inference quirk that prevents the + // following code from being compiled: + // Comparator> comparator = null; + // Map, String> map = newTreeMap(comparator); + return new TreeMap(comparator); + } + + /** + * Creates an {@code EnumMap} instance. + * + * @param type the key type for this map + * @return a new, empty {@code EnumMap} + */ + public static , V> EnumMap newEnumMap(Class type) { + return new EnumMap(checkNotNull(type)); + } + + /** + * Creates an {@code EnumMap} with the same mappings as the specified map. + * + * @param map the map from which to initialize this {@code EnumMap} + * @return a new {@code EnumMap} initialized with the mappings from {@code + * map} + * @throws IllegalArgumentException if {@code m} is not an {@code EnumMap} + * instance and contains no mappings + */ + public static , V> EnumMap newEnumMap( + Map map) { + return new EnumMap(map); + } + + /** + * Creates an {@code IdentityHashMap} instance. + * + * @return a new, empty {@code IdentityHashMap} + */ + public static IdentityHashMap newIdentityHashMap() { + return new IdentityHashMap(); + } + + /** + * Computes the difference between two maps. This difference is an immutable + * snapshot of the state of the maps at the time this method is called. It + * will never change, even if the maps change at a later time. + * + *

Since this method uses {@code HashMap} instances internally, the keys of + * the supplied maps must be well-behaved with respect to + * {@link Object#equals} and {@link Object#hashCode}. + * + *

Note:If you only need to know whether two maps have the same + * mappings, call {@code left.equals(right)} instead of this method. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @return the difference between the two maps + */ + @SuppressWarnings("unchecked") + public static MapDifference difference( + Map left, Map right) { + if (left instanceof SortedMap) { + SortedMap sortedLeft = (SortedMap) left; + SortedMapDifference result = difference(sortedLeft, right); + return result; + } + return difference(left, right, Equivalence.equals()); + } + + /** + * Computes the difference between two maps. This difference is an immutable + * snapshot of the state of the maps at the time this method is called. It + * will never change, even if the maps change at a later time. + * + *

Values are compared using a provided equivalence, in the case of + * equality, the value on the 'left' is returned in the difference. + * + *

Since this method uses {@code HashMap} instances internally, the keys of + * the supplied maps must be well-behaved with respect to + * {@link Object#equals} and {@link Object#hashCode}. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @param valueEquivalence the equivalence relationship to use to compare + * values + * @return the difference between the two maps + * @since 10.0 + */ + @Beta + public static MapDifference difference( + Map left, Map right, + Equivalence valueEquivalence) { + Preconditions.checkNotNull(valueEquivalence); + + Map onlyOnLeft = newHashMap(); + Map onlyOnRight = new HashMap(right); // will whittle it down + Map onBoth = newHashMap(); + Map> differences = newHashMap(); + boolean eq = true; + + for (Entry entry : left.entrySet()) { + K leftKey = entry.getKey(); + V leftValue = entry.getValue(); + if (right.containsKey(leftKey)) { + V rightValue = onlyOnRight.remove(leftKey); + if (valueEquivalence.equivalent(leftValue, rightValue)) { + onBoth.put(leftKey, leftValue); + } else { + eq = false; + differences.put( + leftKey, ValueDifferenceImpl.create(leftValue, rightValue)); + } + } else { + eq = false; + onlyOnLeft.put(leftKey, leftValue); + } + } + + boolean areEqual = eq && onlyOnRight.isEmpty(); + return mapDifference( + areEqual, onlyOnLeft, onlyOnRight, onBoth, differences); + } + + private static MapDifference mapDifference(boolean areEqual, + Map onlyOnLeft, Map onlyOnRight, Map onBoth, + Map> differences) { + return new MapDifferenceImpl(areEqual, + Collections.unmodifiableMap(onlyOnLeft), + Collections.unmodifiableMap(onlyOnRight), + Collections.unmodifiableMap(onBoth), + Collections.unmodifiableMap(differences)); + } + + static class MapDifferenceImpl implements MapDifference { + final boolean areEqual; + final Map onlyOnLeft; + final Map onlyOnRight; + final Map onBoth; + final Map> differences; + + MapDifferenceImpl(boolean areEqual, Map onlyOnLeft, + Map onlyOnRight, Map onBoth, + Map> differences) { + this.areEqual = areEqual; + this.onlyOnLeft = onlyOnLeft; + this.onlyOnRight = onlyOnRight; + this.onBoth = onBoth; + this.differences = differences; + } + + @Override + public boolean areEqual() { + return areEqual; + } + + @Override + public Map entriesOnlyOnLeft() { + return onlyOnLeft; + } + + @Override + public Map entriesOnlyOnRight() { + return onlyOnRight; + } + + @Override + public Map entriesInCommon() { + return onBoth; + } + + @Override + public Map> entriesDiffering() { + return differences; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof MapDifference) { + MapDifference other = (MapDifference) object; + return entriesOnlyOnLeft().equals(other.entriesOnlyOnLeft()) + && entriesOnlyOnRight().equals(other.entriesOnlyOnRight()) + && entriesInCommon().equals(other.entriesInCommon()) + && entriesDiffering().equals(other.entriesDiffering()); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(entriesOnlyOnLeft(), entriesOnlyOnRight(), + entriesInCommon(), entriesDiffering()); + } + + @Override public String toString() { + if (areEqual) { + return "equal"; + } + + StringBuilder result = new StringBuilder("not equal"); + if (!onlyOnLeft.isEmpty()) { + result.append(": only on left=").append(onlyOnLeft); + } + if (!onlyOnRight.isEmpty()) { + result.append(": only on right=").append(onlyOnRight); + } + if (!differences.isEmpty()) { + result.append(": value differences=").append(differences); + } + return result.toString(); + } + } + + static class ValueDifferenceImpl + implements MapDifference.ValueDifference { + private final V left; + private final V right; + + static ValueDifference create(@Nullable V left, @Nullable V right) { + return new ValueDifferenceImpl(left, right); + } + + private ValueDifferenceImpl(@Nullable V left, @Nullable V right) { + this.left = left; + this.right = right; + } + + @Override + public V leftValue() { + return left; + } + + @Override + public V rightValue() { + return right; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof MapDifference.ValueDifference) { + MapDifference.ValueDifference that = + (MapDifference.ValueDifference) object; + return Objects.equal(this.left, that.leftValue()) + && Objects.equal(this.right, that.rightValue()); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(left, right); + } + + @Override public String toString() { + return "(" + left + ", " + right + ")"; + } + } + + /** + * Computes the difference between two sorted maps, using the comparator of + * the left map, or {@code Ordering.natural()} if the left map uses the + * natural ordering of its elements. This difference is an immutable snapshot + * of the state of the maps at the time this method is called. It will never + * change, even if the maps change at a later time. + * + *

Since this method uses {@code TreeMap} instances internally, the keys of + * the right map must all compare as distinct according to the comparator + * of the left map. + * + *

Note:If you only need to know whether two sorted maps have the + * same mappings, call {@code left.equals(right)} instead of this method. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @return the difference between the two maps + * @since 11.0 + */ + public static SortedMapDifference difference( + SortedMap left, Map right) { + checkNotNull(left); + checkNotNull(right); + Comparator comparator = orNaturalOrder(left.comparator()); + SortedMap onlyOnLeft = Maps.newTreeMap(comparator); + SortedMap onlyOnRight = Maps.newTreeMap(comparator); + onlyOnRight.putAll(right); // will whittle it down + SortedMap onBoth = Maps.newTreeMap(comparator); + SortedMap> differences = + Maps.newTreeMap(comparator); + boolean eq = true; + + for (Entry entry : left.entrySet()) { + K leftKey = entry.getKey(); + V leftValue = entry.getValue(); + if (right.containsKey(leftKey)) { + V rightValue = onlyOnRight.remove(leftKey); + if (Objects.equal(leftValue, rightValue)) { + onBoth.put(leftKey, leftValue); + } else { + eq = false; + differences.put( + leftKey, ValueDifferenceImpl.create(leftValue, rightValue)); + } + } else { + eq = false; + onlyOnLeft.put(leftKey, leftValue); + } + } + + boolean areEqual = eq && onlyOnRight.isEmpty(); + return sortedMapDifference( + areEqual, onlyOnLeft, onlyOnRight, onBoth, differences); + } + + private static SortedMapDifference sortedMapDifference( + boolean areEqual, SortedMap onlyOnLeft, SortedMap onlyOnRight, + SortedMap onBoth, SortedMap> differences) { + return new SortedMapDifferenceImpl(areEqual, + Collections.unmodifiableSortedMap(onlyOnLeft), + Collections.unmodifiableSortedMap(onlyOnRight), + Collections.unmodifiableSortedMap(onBoth), + Collections.unmodifiableSortedMap(differences)); + } + + static class SortedMapDifferenceImpl extends MapDifferenceImpl + implements SortedMapDifference { + SortedMapDifferenceImpl(boolean areEqual, SortedMap onlyOnLeft, + SortedMap onlyOnRight, SortedMap onBoth, + SortedMap> differences) { + super(areEqual, onlyOnLeft, onlyOnRight, onBoth, differences); + } + + @Override public SortedMap> entriesDiffering() { + return (SortedMap>) super.entriesDiffering(); + } + + @Override public SortedMap entriesInCommon() { + return (SortedMap) super.entriesInCommon(); + } + + @Override public SortedMap entriesOnlyOnLeft() { + return (SortedMap) super.entriesOnlyOnLeft(); + } + + @Override public SortedMap entriesOnlyOnRight() { + return (SortedMap) super.entriesOnlyOnRight(); + } + } + + /** + * Returns the specified comparator if not null; otherwise returns {@code + * Ordering.natural()}. This method is an abomination of generics; the only + * purpose of this method is to contain the ugly type-casting in one place. + */ + @SuppressWarnings("unchecked") + static Comparator orNaturalOrder( + @Nullable Comparator comparator) { + if (comparator != null) { // can't use ? : because of javac bug 5080917 + return comparator; + } + return (Comparator) Ordering.natural(); + } + + /** + * Returns a view of the set as a map, mapping keys from the set according to + * the specified function. + * + *

Specifically, for each {@code k} in the backing set, the returned map + * has an entry mapping {@code k} to {@code function.apply(k)}. The {@code + * keySet}, {@code values}, and {@code entrySet} views of the returned map + * iterate in the same order as the backing set. + * + *

Modifications to the backing set are read through to the returned map. + * The returned map supports removal operations if the backing set does. + * Removal operations write through to the backing set. The returned map + * does not support put operations. + * + *

Warning: If the function rejects {@code null}, caution is + * required to make sure the set does not contain {@code null}, because the + * view cannot stop {@code null} from being added to the set. + * + *

Warning: This method assumes that for any instance {@code k} of + * key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also + * of type {@code K}. Using a key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling + * methods on the resulting map view. + */ + @Beta + static Map asMap( + Set set, Function function) { + if (set instanceof SortedSet) { + return asMap((SortedSet) set, function); + } else { + return new AsMapView(set, function); + } + } + + /** + * Returns a view of the sorted set as a map, mapping keys from the set + * according to the specified function. + * + *

Specifically, for each {@code k} in the backing set, the returned map + * has an entry mapping {@code k} to {@code function.apply(k)}. The {@code + * keySet}, {@code values}, and {@code entrySet} views of the returned map + * iterate in the same order as the backing set. + * + *

Modifications to the backing set are read through to the returned map. + * The returned map supports removal operations if the backing set does. + * Removal operations write through to the backing set. The returned map does + * not support put operations. + * + *

Warning: If the function rejects {@code null}, caution is + * required to make sure the set does not contain {@code null}, because the + * view cannot stop {@code null} from being added to the set. + * + *

Warning: This method assumes that for any instance {@code k} of + * key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using a key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling + * methods on the resulting map view. + */ + @Beta + static SortedMap asMap( + SortedSet set, Function function) { + // TODO: NavigableSet overloads + return new SortedAsMapView(set, function); + } + + private static class AsMapView extends ImprovedAbstractMap { + + private final Set set; + final Function function; + + Set backingSet() { + return set; + } + + AsMapView(Set set, Function function) { + this.set = checkNotNull(set); + this.function = checkNotNull(function); + } + + @Override + public Set keySet() { + // probably not worth caching + return new ForwardingSet() { + @Override + protected Set delegate() { + return set; + } + + @Override + public boolean add(K element) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public Collection values() { + // probably not worth caching + return Collections2.transform(set, function); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean containsKey(@Nullable Object key) { + return set.contains(key); + } + + @Override + public V get(@Nullable Object key) { + if (set.contains(key)) { + @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it + K k = (K) key; + return function.apply(k); + } else { + return null; + } + } + + @Override + public V remove(@Nullable Object key) { + if (set.remove(key)) { + @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it + K k = (K) key; + return function.apply(k); + } else { + return null; + } + } + + @Override + public void clear() { + set.clear(); + } + + @Override + protected Set> createEntrySet() { + return new EntrySet() { + @Override + Map map() { + return AsMapView.this; + } + + @Override + public Iterator> iterator() { + final Iterator backingIterator = set.iterator(); + return new Iterator>() { + @Override + public boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public Entry next() { + K k = backingIterator.next(); + return Maps.immutableEntry(k, function.apply(k)); + } + + @Override + public void remove() { + backingIterator.remove(); + } + }; + } + }; + } + } + + private static final class SortedAsMapView extends AsMapView + implements SortedMap { + + SortedAsMapView(SortedSet set, Function function) { + super(set, function); + } + + @Override + public Comparator comparator() { + return backingSet().comparator(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return asMap(backingSet().subSet(fromKey, toKey), function); + } + + @Override + public SortedMap headMap(K toKey) { + return asMap(backingSet().headSet(toKey), function); + } + + @Override + public SortedMap tailMap(K fromKey) { + return asMap(backingSet().tailSet(fromKey), function); + } + + @Override + public K firstKey() { + return backingSet().first(); + } + + @Override + public K lastKey() { + return backingSet().last(); + } + + @Override + SortedSet backingSet() { + return (SortedSet) super.backingSet(); + } + } + + /** + * Returns an immutable map for which the {@link Map#values} are the given + * elements in the given order, and each key is the product of invoking a + * supplied function on its corresponding value. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same + * key for more than one value in the input collection + * @throws NullPointerException if any elements of {@code values} is null, or + * if {@code keyFunction} produces {@code null} for any value + */ + public static ImmutableMap uniqueIndex( + Iterable values, Function keyFunction) { + return uniqueIndex(values.iterator(), keyFunction); + } + + /** + * Returns an immutable map for which the {@link Map#values} are the given + * elements in the given order, and each key is the product of invoking a + * supplied function on its corresponding value. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same + * key for more than one value in the input collection + * @throws NullPointerException if any elements of {@code values} is null, or + * if {@code keyFunction} produces {@code null} for any value + * @since 10.0 + */ + public static ImmutableMap uniqueIndex( + Iterator values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableMap.Builder builder = ImmutableMap.builder(); + while (values.hasNext()) { + V value = values.next(); + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + /** + * Creates an {@code ImmutableMap} from a {@code Properties} + * instance. Properties normally derive from {@code Map}, but + * they typically contain strings, which is awkward. This method lets you get + * a plain-old-{@code Map} out of a {@code Properties}. + * + * @param properties a {@code Properties} object to be converted + * @return an immutable map containing all the entries in {@code properties} + * @throws ClassCastException if any key in {@code Properties} is not a {@code + * String} + * @throws NullPointerException if any key or value in {@code Properties} is + * null + */ + @GwtIncompatible("java.util.Properties") + public static ImmutableMap fromProperties( + Properties properties) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + builder.put(key, properties.getProperty(key)); + } + + return builder.build(); + } + + /** + * Returns an immutable map entry with the specified key and value. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * + *

The returned entry is serializable. + * + * @param key the key to be associated with the returned entry + * @param value the value to be associated with the returned entry + */ + @GwtCompatible(serializable = true) + public static Entry immutableEntry( + @Nullable K key, @Nullable V value) { + return new ImmutableEntry(key, value); + } + + /** + * Returns an unmodifiable view of the specified set of entries. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}, + * as do any operations that would modify the returned set. + * + * @param entrySet the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + static Set> unmodifiableEntrySet( + Set> entrySet) { + return new UnmodifiableEntrySet( + Collections.unmodifiableSet(entrySet)); + } + + /** + * Returns an unmodifiable view of the specified map entry. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * This also has the side-effect of redefining {@code equals} to comply with + * the Entry contract, to avoid a possible nefarious implementation of equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + static Entry unmodifiableEntry(final Entry entry) { + checkNotNull(entry); + return new AbstractMapEntry() { + @Override public K getKey() { + return entry.getKey(); + } + + @Override public V getValue() { + return entry.getValue(); + } + }; + } + + /** @see Multimaps#unmodifiableEntries */ + static class UnmodifiableEntries + extends ForwardingCollection> { + private final Collection> entries; + + UnmodifiableEntries(Collection> entries) { + this.entries = entries; + } + + @Override protected Collection> delegate() { + return entries; + } + + @Override public Iterator> iterator() { + final Iterator> delegate = super.iterator(); + return new ForwardingIterator>() { + @Override public Entry next() { + return unmodifiableEntry(super.next()); + } + + @Override public void remove() { + throw new UnsupportedOperationException(); + } + + @Override protected Iterator> delegate() { + return delegate; + } + }; + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public boolean add(Entry element) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll( + Collection> collection) { + throw new UnsupportedOperationException(); + } + + @Override public void clear() { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + @Override public boolean removeAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override public boolean retainAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override public Object[] toArray() { + return standardToArray(); + } + + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + } + + /** @see Maps#unmodifiableEntrySet(Set) */ + static class UnmodifiableEntrySet + extends UnmodifiableEntries implements Set> { + UnmodifiableEntrySet(Set> entries) { + super(entries); + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public boolean equals(@Nullable Object object) { + return Sets.equalsImpl(this, object); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** + * Returns a synchronized (thread-safe) bimap backed by the specified bimap. + * In order to guarantee serial access, it is critical that all access + * to the backing bimap is accomplished through the returned bimap. + * + *

It is imperative that the user manually synchronize on the returned map + * when accessing any of its collection views:

   {@code
+   *
+   *   BiMap map = Maps.synchronizedBiMap(
+   *       HashBiMap.create());
+   *   ...
+   *   Set set = map.keySet();  // Needn't be in synchronized block
+   *   ...
+   *   synchronized (map) {  // Synchronizing on map, not set!
+   *     Iterator it = set.iterator(); // Must be in synchronized block
+   *     while (it.hasNext()) {
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned bimap will be serializable if the specified bimap is + * serializable. + * + * @param bimap the bimap to be wrapped in a synchronized view + * @return a sychronized view of the specified bimap + */ + public static BiMap synchronizedBiMap(BiMap bimap) { + return Synchronized.biMap(bimap, null); + } + + /** + * Returns an unmodifiable view of the specified bimap. This method allows + * modules to provide users with "read-only" access to internal bimaps. Query + * operations on the returned bimap "read through" to the specified bimap, and + * attempts to modify the returned map, whether direct or via its collection + * views, result in an {@code UnsupportedOperationException}. + * + *

The returned bimap will be serializable if the specified bimap is + * serializable. + * + * @param bimap the bimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified bimap + */ + public static BiMap unmodifiableBiMap( + BiMap bimap) { + return new UnmodifiableBiMap(bimap, null); + } + + /** @see Maps#unmodifiableBiMap(BiMap) */ + private static class UnmodifiableBiMap + extends ForwardingMap implements BiMap, Serializable { + final Map unmodifiableMap; + final BiMap delegate; + BiMap inverse; + transient Set values; + + UnmodifiableBiMap(BiMap delegate, + @Nullable BiMap inverse) { + unmodifiableMap = Collections.unmodifiableMap(delegate); + this.delegate = delegate; + this.inverse = inverse; + } + + @Override protected Map delegate() { + return unmodifiableMap; + } + + @Override + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public BiMap inverse() { + BiMap result = inverse; + return (result == null) + ? inverse = new UnmodifiableBiMap(delegate.inverse(), this) + : result; + } + + @Override public Set values() { + Set result = values; + return (result == null) + ? values = Collections.unmodifiableSet(delegate.values()) + : result; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a view of a map where each value is transformed by a function. All + * other properties of the map, such as iteration order, are left intact. For + * example, the code:

   {@code
+   *
+   *   Map map = ImmutableMap.of("a", 4, "b", 9);
+   *   Function sqrt =
+   *       new Function() {
+   *         public Double apply(Integer in) {
+   *           return Math.sqrt((int) in);
+   *         }
+   *       };
+   *   Map transformed = Maps.transformValues(map, sqrt);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even + * null values provided that the function is capable of accepting null input. + * The transformed map might contain null values, if the function sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned map to be a view, but it means that the function will be + * applied many times for bulk operations like {@link Map#containsValue} and + * {@code Map.toString()}. For this to perform well, {@code function} should + * be fast. To avoid lazy evaluation when the returned map doesn't need to be + * a view, copy the returned map into a new map of your choosing. + */ + public static Map transformValues( + Map fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + /** + * Returns a view of a sorted map where each value is transformed by a + * function. All other properties of the map, such as iteration order, are + * left intact. For example, the code:

   {@code
+   *
+   *   SortedMap map = ImmutableSortedMap.of("a", 4, "b", 9);
+   *   Function sqrt =
+   *       new Function() {
+   *         public Double apply(Integer in) {
+   *           return Math.sqrt((int) in);
+   *         }
+   *       };
+   *   SortedMap transformed =
+   *        Maps.transformSortedValues(map, sqrt);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even + * null values provided that the function is capable of accepting null input. + * The transformed map might contain null values, if the function sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned map to be a view, but it means that the function will be + * applied many times for bulk operations like {@link Map#containsValue} and + * {@code Map.toString()}. For this to perform well, {@code function} should + * be fast. To avoid lazy evaluation when the returned map doesn't need to be + * a view, copy the returned map into a new map of your choosing. + * + * @since 11.0 + */ + @Beta + public static SortedMap transformValues( + SortedMap fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + /** + * Returns a view of a navigable map where each value is transformed by a + * function. All other properties of the map, such as iteration order, are + * left intact. For example, the code:

   {@code
+   *
+   *   NavigableMap map = Maps.newTreeMap();
+   *   map.put("a", 4);
+   *   map.put("b", 9);
+   *   Function sqrt =
+   *       new Function() {
+   *         public Double apply(Integer in) {
+   *           return Math.sqrt((int) in);
+   *         }
+   *       };
+   *   NavigableMap transformed =
+   *        Maps.transformNavigableValues(map, sqrt);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + * Changes in the underlying map are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even + * null values provided that the function is capable of accepting null input. + * The transformed map might contain null values, if the function sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned map to be a view, but it means that the function will be + * applied many times for bulk operations like {@link Map#containsValue} and + * {@code Map.toString()}. For this to perform well, {@code function} should + * be fast. To avoid lazy evaluation when the returned map doesn't need to be + * a view, copy the returned map into a new map of your choosing. + * + * @since 13.0 + */ + @Beta + @GwtIncompatible("NavigableMap") + public static NavigableMap transformValues( + NavigableMap fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + private static EntryTransformer + asEntryTransformer(final Function function) { + checkNotNull(function); + return new EntryTransformer() { + @Override + public V2 transformEntry(K key, V1 value) { + return function.apply(value); + } + }; + } + + /** + * Returns a view of a map whose values are derived from the original map's + * entries. In contrast to {@link #transformValues}, this method's + * entry-transformation logic may depend on the key as well as the value. + * + *

All other properties of the transformed map, such as iteration order, + * are left intact. For example, the code:

   {@code
+   *
+   *   Map options =
+   *       ImmutableMap.of("verbose", true, "sort", false);
+   *   EntryTransformer flagPrefixer =
+   *       new EntryTransformer() {
+   *         public String transformEntry(String key, Boolean value) {
+   *           return value ? key : "no" + key;
+   *         }
+   *       };
+   *   Map transformed =
+   *       Maps.transformEntries(options, flagPrefixer);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {verbose=verbose, sort=nosort}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null + * values provided that the transformer is capable of accepting null inputs. + * The transformed map might contain null values if the transformer sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is + * necessary for the returned map to be a view, but it means that the + * transformer will be applied many times for bulk operations like {@link + * Map#containsValue} and {@link Object#toString}. For this to perform well, + * {@code transformer} should be fast. To avoid lazy evaluation when the + * returned map doesn't need to be a view, copy the returned map into a new + * map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of + * {@code EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies + * that {@code k2} is also of type {@code K}. Using an {@code + * EntryTransformer} key type for which this may not hold, such as {@code + * ArrayList}, may risk a {@code ClassCastException} when calling methods on + * the transformed map. + * + * @since 7.0 + */ + public static Map transformEntries( + Map fromMap, + EntryTransformer transformer) { + if (fromMap instanceof SortedMap) { + return transformEntries((SortedMap) fromMap, transformer); + } + return new TransformedEntriesMap(fromMap, transformer); + } + + /** + * Returns a view of a sorted map whose values are derived from the original + * sorted map's entries. In contrast to {@link #transformValues}, this + * method's entry-transformation logic may depend on the key as well as the + * value. + * + *

All other properties of the transformed map, such as iteration order, + * are left intact. For example, the code:

   {@code
+   *
+   *   Map options =
+   *       ImmutableSortedMap.of("verbose", true, "sort", false);
+   *   EntryTransformer flagPrefixer =
+   *       new EntryTransformer() {
+   *         public String transformEntry(String key, Boolean value) {
+   *           return value ? key : "yes" + key;
+   *         }
+   *       };
+   *   SortedMap transformed =
+   *       LabsMaps.transformSortedEntries(options, flagPrefixer);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {sort=yessort, verbose=verbose}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null + * values provided that the transformer is capable of accepting null inputs. + * The transformed map might contain null values if the transformer sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is + * necessary for the returned map to be a view, but it means that the + * transformer will be applied many times for bulk operations like {@link + * Map#containsValue} and {@link Object#toString}. For this to perform well, + * {@code transformer} should be fast. To avoid lazy evaluation when the + * returned map doesn't need to be a view, copy the returned map into a new + * map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of + * {@code EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies + * that {@code k2} is also of type {@code K}. Using an {@code + * EntryTransformer} key type for which this may not hold, such as {@code + * ArrayList}, may risk a {@code ClassCastException} when calling methods on + * the transformed map. + * + * @since 11.0 + */ + @Beta + public static SortedMap transformEntries( + SortedMap fromMap, + EntryTransformer transformer) { + return Platform.mapsTransformEntriesSortedMap(fromMap, transformer); + } + + /** + * Returns a view of a navigable map whose values are derived from the + * original navigable map's entries. In contrast to {@link + * #transformValues}, this method's entry-transformation logic may + * depend on the key as well as the value. + * + *

All other properties of the transformed map, such as iteration order, + * are left intact. For example, the code:

   {@code
+   *
+   *   NavigableMap options = Maps.newTreeMap();
+   *   options.put("verbose", false);
+   *   options.put("sort", true);
+   *   EntryTransformer flagPrefixer =
+   *       new EntryTransformer() {
+   *         public String transformEntry(String key, Boolean value) {
+   *           return value ? key : ("yes" + key);
+   *         }
+   *       };
+   *   NavigableMap transformed =
+   *       LabsMaps.transformNavigableEntries(options, flagPrefixer);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {sort=yessort, verbose=verbose}}. + * + *

Changes in the underlying map are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null + * values provided that the transformer is capable of accepting null inputs. + * The transformed map might contain null values if the transformer sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is + * necessary for the returned map to be a view, but it means that the + * transformer will be applied many times for bulk operations like {@link + * Map#containsValue} and {@link Object#toString}. For this to perform well, + * {@code transformer} should be fast. To avoid lazy evaluation when the + * returned map doesn't need to be a view, copy the returned map into a new + * map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of + * {@code EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies + * that {@code k2} is also of type {@code K}. Using an {@code + * EntryTransformer} key type for which this may not hold, such as {@code + * ArrayList}, may risk a {@code ClassCastException} when calling methods on + * the transformed map. + * + * @since 13.0 + */ + @Beta + @GwtIncompatible("NavigableMap") + public static NavigableMap transformEntries( + final NavigableMap fromMap, + EntryTransformer transformer) { + return new TransformedEntriesNavigableMap(fromMap, transformer); + } + + static SortedMap transformEntriesIgnoreNavigable( + SortedMap fromMap, + EntryTransformer transformer) { + return new TransformedEntriesSortedMap(fromMap, transformer); + } + + /** + * A transformation of the value of a key-value pair, using both key and value + * as inputs. To apply the transformation to a map, use + * {@link Maps#transformEntries(Map, EntryTransformer)}. + * + * @param the key type of the input and output entries + * @param the value type of the input entry + * @param the value type of the output entry + * @since 7.0 + */ + public interface EntryTransformer { + /** + * Determines an output value based on a key-value pair. This method is + * generally expected, but not absolutely required, to have the + * following properties: + * + *

    + *
  • Its execution does not cause any observable side effects. + *
  • The computation is consistent with equals; that is, + * {@link Objects#equal Objects.equal}{@code (k1, k2) &&} + * {@link Objects#equal}{@code (v1, v2)} implies that {@code + * Objects.equal(transformer.transform(k1, v1), + * transformer.transform(k2, v2))}. + *
+ * + * @throws NullPointerException if the key or value is null and this + * transformer does not accept null arguments + */ + V2 transformEntry(@Nullable K key, @Nullable V1 value); + } + + static class TransformedEntriesMap + extends AbstractMap { + final Map fromMap; + final EntryTransformer transformer; + + TransformedEntriesMap( + Map fromMap, + EntryTransformer transformer) { + this.fromMap = checkNotNull(fromMap); + this.transformer = checkNotNull(transformer); + } + + @Override public int size() { + return fromMap.size(); + } + + @Override public boolean containsKey(Object key) { + return fromMap.containsKey(key); + } + + // safe as long as the user followed the Warning in the javadoc + @SuppressWarnings("unchecked") + @Override public V2 get(Object key) { + V1 value = fromMap.get(key); + return (value != null || fromMap.containsKey(key)) + ? transformer.transformEntry((K) key, value) + : null; + } + + // safe as long as the user followed the Warning in the javadoc + @SuppressWarnings("unchecked") + @Override public V2 remove(Object key) { + return fromMap.containsKey(key) + ? transformer.transformEntry((K) key, fromMap.remove(key)) + : null; + } + + @Override public void clear() { + fromMap.clear(); + } + + @Override public Set keySet() { + return fromMap.keySet(); + } + + Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = new EntrySet() { + @Override Map map() { + return TransformedEntriesMap.this; + } + + @Override public Iterator> iterator() { + return new TransformedIterator, Entry>( + fromMap.entrySet().iterator()) { + @Override + Entry transform(final Entry entry) { + return new AbstractMapEntry() { + @Override + public K getKey() { + return entry.getKey(); + } + + @Override + public V2 getValue() { + return transformer.transformEntry(entry.getKey(), entry.getValue()); + } + }; + } + }; + } + }; + } + return result; + } + + Collection values; + + @Override public Collection values() { + Collection result = values; + if (result == null) { + return values = new Values() { + @Override Map map() { + return TransformedEntriesMap.this; + } + }; + } + return result; + } + } + + static class TransformedEntriesSortedMap + extends TransformedEntriesMap implements SortedMap { + + protected SortedMap fromMap() { + return (SortedMap) fromMap; + } + + TransformedEntriesSortedMap(SortedMap fromMap, + EntryTransformer transformer) { + super(fromMap, transformer); + } + + @Override public Comparator comparator() { + return fromMap().comparator(); + } + + @Override public K firstKey() { + return fromMap().firstKey(); + } + + @Override public SortedMap headMap(K toKey) { + return transformEntries(fromMap().headMap(toKey), transformer); + } + + @Override public K lastKey() { + return fromMap().lastKey(); + } + + @Override public SortedMap subMap(K fromKey, K toKey) { + return transformEntries( + fromMap().subMap(fromKey, toKey), transformer); + } + + @Override public SortedMap tailMap(K fromKey) { + return transformEntries(fromMap().tailMap(fromKey), transformer); + } + } + + @GwtIncompatible("NavigableMap") + private static class TransformedEntriesNavigableMap + extends TransformedEntriesSortedMap + implements NavigableMap { + + TransformedEntriesNavigableMap(NavigableMap fromMap, + EntryTransformer transformer) { + super(fromMap, transformer); + } + + @Override public Entry ceilingEntry(K key) { + return transformEntry(fromMap().ceilingEntry(key)); + } + + @Override public K ceilingKey(K key) { + return fromMap().ceilingKey(key); + } + + @Override public NavigableSet descendingKeySet() { + return fromMap().descendingKeySet(); + } + + @Override public NavigableMap descendingMap() { + return transformEntries(fromMap().descendingMap(), transformer); + } + + @Override public Entry firstEntry() { + return transformEntry(fromMap().firstEntry()); + } + @Override public Entry floorEntry(K key) { + return transformEntry(fromMap().floorEntry(key)); + } + + @Override public K floorKey(K key) { + return fromMap().floorKey(key); + } + + @Override public NavigableMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override public NavigableMap headMap(K toKey, boolean inclusive) { + return transformEntries( + fromMap().headMap(toKey, inclusive), transformer); + } + + @Override public Entry higherEntry(K key) { + return transformEntry(fromMap().higherEntry(key)); + } + + @Override public K higherKey(K key) { + return fromMap().higherKey(key); + } + + @Override public Entry lastEntry() { + return transformEntry(fromMap().lastEntry()); + } + + @Override public Entry lowerEntry(K key) { + return transformEntry(fromMap().lowerEntry(key)); + } + + @Override public K lowerKey(K key) { + return fromMap().lowerKey(key); + } + + @Override public NavigableSet navigableKeySet() { + return fromMap().navigableKeySet(); + } + + @Override public Entry pollFirstEntry() { + return transformEntry(fromMap().pollFirstEntry()); + } + + @Override public Entry pollLastEntry() { + return transformEntry(fromMap().pollLastEntry()); + } + + @Override public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return transformEntries( + fromMap().subMap(fromKey, fromInclusive, toKey, toInclusive), + transformer); + } + + @Override public NavigableMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override public NavigableMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override public NavigableMap tailMap(K fromKey, boolean inclusive) { + return transformEntries( + fromMap().tailMap(fromKey, inclusive), transformer); + } + + private Entry transformEntry(Entry entry) { + if (entry == null) { + return null; + } + K key = entry.getKey(); + V2 v2 = transformer.transformEntry(key, entry.getValue()); + return Maps.immutableEntry(key, v2); + } + + @Override protected NavigableMap fromMap() { + return (NavigableMap) super.fromMap(); + } + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose keys + * satisfy a predicate. The returned map is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a key that + * doesn't satisfy the predicate, the map's {@code put()} and {@code putAll()} + * methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose keys satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. Do not provide a + * predicate such as {@code Predicates.instanceOf(ArrayList.class)}, which is + * inconsistent with equals. + */ + public static Map filterKeys( + Map unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof SortedMap) { + return filterKeys((SortedMap) unfiltered, keyPredicate); + } + checkNotNull(keyPredicate); + Predicate> entryPredicate = + new Predicate>() { + @Override + public boolean apply(Entry input) { + return keyPredicate.apply(input.getKey()); + } + }; + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredKeyMap( + checkNotNull(unfiltered), keyPredicate, entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} whose + * keys satisfy a predicate. The returned map is a live view of {@code + * unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a key that + * doesn't satisfy the predicate, the map's {@code put()} and {@code putAll()} + * methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose keys satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. Do not provide a + * predicate such as {@code Predicates.instanceOf(ArrayList.class)}, which is + * inconsistent with equals. + * + * @since 11.0 + */ + public static SortedMap filterKeys( + SortedMap unfiltered, final Predicate keyPredicate) { + // TODO: Return a subclass of Maps.FilteredKeyMap for slightly better + // performance. + checkNotNull(keyPredicate); + Predicate> entryPredicate = new Predicate>() { + @Override + public boolean apply(Entry input) { + return keyPredicate.apply(input.getKey()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose values + * satisfy a predicate. The returned map is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a value + * that doesn't satisfy the predicate, the map's {@code put()}, {@code + * putAll()}, and {@link Entry#setValue} methods throw an {@link + * IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose values satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. Do not provide a + * predicate such as {@code Predicates.instanceOf(ArrayList.class)}, which is + * inconsistent with equals. + */ + public static Map filterValues( + Map unfiltered, final Predicate valuePredicate) { + if (unfiltered instanceof SortedMap) { + return filterValues((SortedMap) unfiltered, valuePredicate); + } + checkNotNull(valuePredicate); + Predicate> entryPredicate = + new Predicate>() { + @Override + public boolean apply(Entry input) { + return valuePredicate.apply(input.getValue()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} whose + * values satisfy a predicate. The returned map is a live view of {@code + * unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a value + * that doesn't satisfy the predicate, the map's {@code put()}, {@code + * putAll()}, and {@link Entry#setValue} methods throw an {@link + * IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose values satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. Do not provide a + * predicate such as {@code Predicates.instanceOf(ArrayList.class)}, which is + * inconsistent with equals. + * + * @since 11.0 + */ + public static SortedMap filterValues( + SortedMap unfiltered, final Predicate valuePredicate) { + checkNotNull(valuePredicate); + Predicate> entryPredicate = + new Predicate>() { + @Override + public boolean apply(Entry input) { + return valuePredicate.apply(input.getValue()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} that satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes + * to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a + * key/value pair that doesn't satisfy the predicate, the map's {@code put()} + * and {@code putAll()} methods throw an {@link IllegalArgumentException}. + * Similarly, the map's entries have a {@link Entry#setValue} method that + * throws an {@link IllegalArgumentException} when the existing key and the + * provided value don't satisfy the predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings that satisfy the filter + * will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. + */ + public static Map filterEntries( + Map unfiltered, Predicate> entryPredicate) { + if (unfiltered instanceof SortedMap) { + return filterEntries((SortedMap) unfiltered, entryPredicate); + } + checkNotNull(entryPredicate); + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredEntryMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} that + * satisfy a predicate. The returned map is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. When given a + * key/value pair that doesn't satisfy the predicate, the map's {@code put()} + * and {@code putAll()} methods throw an {@link IllegalArgumentException}. + * Similarly, the map's entries have a {@link Entry#setValue} method that + * throws an {@link IllegalArgumentException} when the existing key and the + * provided value don't satisfy the predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings that satisfy the filter + * will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. + * + * @since 11.0 + */ + public static SortedMap filterEntries( + SortedMap unfiltered, + Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredEntrySortedMap) + ? filterFiltered((FilteredEntrySortedMap) unfiltered, entryPredicate) + : new FilteredEntrySortedMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when + * filtering a filtered map. + */ + private static Map filterFiltered(AbstractFilteredMap map, + Predicate> entryPredicate) { + Predicate> predicate = + Predicates.and(map.predicate, entryPredicate); + return new FilteredEntryMap(map.unfiltered, predicate); + } + + private abstract static class AbstractFilteredMap + extends AbstractMap { + final Map unfiltered; + final Predicate> predicate; + + AbstractFilteredMap( + Map unfiltered, Predicate> predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + boolean apply(Object key, V value) { + // This method is called only when the key is in the map, implying that + // key is a K. + @SuppressWarnings("unchecked") + K k = (K) key; + return predicate.apply(Maps.immutableEntry(k, value)); + } + + @Override public V put(K key, V value) { + checkArgument(apply(key, value)); + return unfiltered.put(key, value); + } + + @Override public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + checkArgument(apply(entry.getKey(), entry.getValue())); + } + unfiltered.putAll(map); + } + + @Override public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && apply(key, unfiltered.get(key)); + } + + @Override public V get(Object key) { + V value = unfiltered.get(key); + return ((value != null) && apply(key, value)) ? value : null; + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public V remove(Object key) { + return containsKey(key) ? unfiltered.remove(key) : null; + } + + Collection values; + + @Override public Collection values() { + Collection result = values; + return (result == null) ? values = new Values() : result; + } + + class Values extends AbstractCollection { + @Override public Iterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public V next() { + return entryIterator.next().getValue(); + } + }; + } + + @Override public int size() { + return entrySet().size(); + } + + @Override public void clear() { + entrySet().clear(); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public boolean remove(Object o) { + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (Objects.equal(o, entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection collection) { + checkNotNull(collection); + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (collection.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public boolean retainAll(Collection collection) { + checkNotNull(collection); + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (!collection.contains(entry.getValue()) + && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + } + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when + * filtering a filtered sorted map. + */ + private static SortedMap filterFiltered( + FilteredEntrySortedMap map, + Predicate> entryPredicate) { + Predicate> predicate + = Predicates.and(map.predicate, entryPredicate); + return new FilteredEntrySortedMap(map.sortedMap(), predicate); + } + + private static class FilteredEntrySortedMap + extends FilteredEntryMap implements SortedMap { + + FilteredEntrySortedMap(SortedMap unfiltered, + Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + } + + SortedMap sortedMap() { + return (SortedMap) unfiltered; + } + + @Override public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override public K firstKey() { + // correctly throws NoSuchElementException when filtered map is empty. + return keySet().iterator().next(); + } + + @Override public K lastKey() { + SortedMap headMap = sortedMap(); + while (true) { + // correctly throws NoSuchElementException when filtered map is empty. + K key = headMap.lastKey(); + if (apply(key, unfiltered.get(key))) { + return key; + } + headMap = sortedMap().headMap(key); + } + } + + @Override public SortedMap headMap(K toKey) { + return new FilteredEntrySortedMap(sortedMap().headMap(toKey), predicate); + } + + @Override public SortedMap subMap(K fromKey, K toKey) { + return new FilteredEntrySortedMap( + sortedMap().subMap(fromKey, toKey), predicate); + } + + @Override public SortedMap tailMap(K fromKey) { + return new FilteredEntrySortedMap( + sortedMap().tailMap(fromKey), predicate); + } + } + + private static class FilteredKeyMap extends AbstractFilteredMap { + Predicate keyPredicate; + + FilteredKeyMap(Map unfiltered, Predicate keyPredicate, + Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + this.keyPredicate = keyPredicate; + } + + Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) + ? entrySet = Sets.filter(unfiltered.entrySet(), predicate) + : result; + } + + Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) + ? keySet = Sets.filter(unfiltered.keySet(), keyPredicate) + : result; + } + + // The cast is called only when the key is in the unfiltered map, implying + // that key is a K. + @Override + @SuppressWarnings("unchecked") + public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && keyPredicate.apply((K) key); + } + } + + static class FilteredEntryMap extends AbstractFilteredMap { + /** + * Entries in this set satisfy the predicate, but they don't validate the + * input to {@code Entry.setValue()}. + */ + final Set> filteredEntrySet; + + FilteredEntryMap( + Map unfiltered, Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + filteredEntrySet = Sets.filter(unfiltered.entrySet(), predicate); + } + + Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends ForwardingSet> { + @Override protected Set> delegate() { + return filteredEntrySet; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = filteredEntrySet.iterator(); + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + final Entry entry = iterator.next(); + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return entry; + } + + @Override public V setValue(V value) { + checkArgument(apply(entry.getKey(), value)); + return super.setValue(value); + } + }; + } + }; + } + } + + Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = new KeySet() : result; + } + + private class KeySet extends Sets.ImprovedAbstractSet { + @Override public Iterator iterator() { + final Iterator> iterator = filteredEntrySet.iterator(); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + return iterator.next().getKey(); + } + }; + } + + @Override public int size() { + return filteredEntrySet.size(); + } + + @Override public void clear() { + filteredEntrySet.clear(); + } + + @Override public boolean contains(Object o) { + return containsKey(o); + } + + @Override public boolean remove(Object o) { + if (containsKey(o)) { + unfiltered.remove(o); + return true; + } + return false; + } + + @Override public boolean retainAll(Collection collection) { + checkNotNull(collection); // for GWT + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (predicate.apply(entry) && !collection.contains(entry.getKey())) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + } + + /** + * Returns an unmodifiable view of the specified navigable map. Query operations on the returned + * map read through to the specified map, and attempts to modify the returned map, whether direct + * or via its views, result in an {@code UnsupportedOperationException}. + * + *

The returned navigable map will be serializable if the specified navigable map is + * serializable. + * + * @param map the navigable map for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified navigable map + * @since 12.0 + */ + @GwtIncompatible("NavigableMap") + public static NavigableMap unmodifiableNavigableMap(NavigableMap map) { + checkNotNull(map); + if (map instanceof UnmodifiableNavigableMap) { + return map; + } else { + return new UnmodifiableNavigableMap(map); + } + } + + @Nullable private static Entry unmodifiableOrNull(@Nullable Entry entry) { + return (entry == null) ? null : Maps.unmodifiableEntry(entry); + } + + @GwtIncompatible("NavigableMap") + static class UnmodifiableNavigableMap + extends ForwardingSortedMap implements NavigableMap, Serializable { + private final NavigableMap delegate; + + UnmodifiableNavigableMap(NavigableMap delegate) { + this.delegate = delegate; + } + + @Override + protected SortedMap delegate() { + return Collections.unmodifiableSortedMap(delegate); + } + + @Override + public Entry lowerEntry(K key) { + return unmodifiableOrNull(delegate.lowerEntry(key)); + } + + @Override + public K lowerKey(K key) { + return delegate.lowerKey(key); + } + + @Override + public Entry floorEntry(K key) { + return unmodifiableOrNull(delegate.floorEntry(key)); + } + + @Override + public K floorKey(K key) { + return delegate.floorKey(key); + } + + @Override + public Entry ceilingEntry(K key) { + return unmodifiableOrNull(delegate.ceilingEntry(key)); + } + + @Override + public K ceilingKey(K key) { + return delegate.ceilingKey(key); + } + + @Override + public Entry higherEntry(K key) { + return unmodifiableOrNull(delegate.higherEntry(key)); + } + + @Override + public K higherKey(K key) { + return delegate.higherKey(key); + } + + @Override + public Entry firstEntry() { + return unmodifiableOrNull(delegate.firstEntry()); + } + + @Override + public Entry lastEntry() { + return unmodifiableOrNull(delegate.lastEntry()); + } + + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + private transient UnmodifiableNavigableMap descendingMap; + + @Override + public NavigableMap descendingMap() { + UnmodifiableNavigableMap result = descendingMap; + if (result == null) { + descendingMap = result = new UnmodifiableNavigableMap(delegate.descendingMap()); + result.descendingMap = this; + } + return result; + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + @Override + public NavigableSet navigableKeySet() { + return Sets.unmodifiableNavigableSet(delegate.navigableKeySet()); + } + + @Override + public NavigableSet descendingKeySet() { + return Sets.unmodifiableNavigableSet(delegate.descendingKeySet()); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public + NavigableMap + subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return Maps.unmodifiableNavigableMap(delegate.subMap( + fromKey, + fromInclusive, + toKey, + toInclusive)); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return Maps.unmodifiableNavigableMap(delegate.headMap(toKey, inclusive)); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return Maps.unmodifiableNavigableMap(delegate.tailMap(fromKey, inclusive)); + } + } + + /** + * Returns a synchronized (thread-safe) navigable map backed by the specified + * navigable map. In order to guarantee serial access, it is critical that + * all access to the backing navigable map is accomplished + * through the returned navigable map (or its views). + * + *

It is imperative that the user manually synchronize on the returned + * navigable map when iterating over any of its collection views, or the + * collections views of any of its {@code descendingMap}, {@code subMap}, + * {@code headMap} or {@code tailMap} views.

   {@code
+   *
+   *   NavigableMap map = synchronizedNavigableMap(new TreeMap());
+   *
+   *   // Needn't be in synchronized block
+   *   NavigableSet set = map.navigableKeySet();
+   *
+   *   synchronized (map) { // Synchronizing on map, not set!
+   *     Iterator it = set.iterator(); // Must be in synchronized block
+   *     while (it.hasNext()){
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * or:
   {@code
+   *
+   *   NavigableMap map = synchronizedNavigableMap(new TreeMap());
+   *   NavigableMap map2 = map.subMap(foo, false, bar, true);
+   *
+   *   // Needn't be in synchronized block
+   *   NavigableSet set2 = map2.descendingKeySet();
+   *
+   *   synchronized (map) { // Synchronizing on map, not map2 or set2!
+   *     Iterator it = set2.iterator(); // Must be in synchronized block
+   *     while (it.hasNext()){
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned navigable map will be serializable if the specified + * navigable map is serializable. + * + * @param navigableMap the navigable map to be "wrapped" in a synchronized + * navigable map. + * @return a synchronized view of the specified navigable map. + * @since 13.0 + */ + @Beta + @GwtIncompatible("NavigableMap") + public static NavigableMap synchronizedNavigableMap( + NavigableMap navigableMap) { + return Synchronized.navigableMap(navigableMap); + } + + /** + * {@code AbstractMap} extension that implements {@link #isEmpty()} as {@code + * entrySet().isEmpty()} instead of {@code size() == 0} to speed up + * implementations where {@code size()} is O(n), and it delegates the {@code + * isEmpty()} methods of its key set and value collection to this + * implementation. + */ + @GwtCompatible + abstract static class ImprovedAbstractMap extends AbstractMap { + /** + * Creates the entry set to be returned by {@link #entrySet()}. This method + * is invoked at most once on a given map, at the time when {@code entrySet} + * is first called. + */ + protected abstract Set> createEntrySet(); + + private Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = createEntrySet(); + } + return result; + } + + private Set keySet; + + @Override public Set keySet() { + Set result = keySet; + if (result == null) { + return keySet = new KeySet() { + @Override Map map() { + return ImprovedAbstractMap.this; + } + }; + } + return result; + } + + private Collection values; + + @Override public Collection values() { + Collection result = values; + if (result == null) { + return values = new Values() { + @Override Map map() { + return ImprovedAbstractMap.this; + } + }; + } + return result; + } + } + + static final MapJoiner STANDARD_JOINER = + Collections2.STANDARD_JOINER.withKeyValueSeparator("="); + + /** + * Delegates to {@link Map#get}. Returns {@code null} on {@code + * ClassCastException}. + */ + static V safeGet(Map map, Object key) { + try { + return map.get(key); + } catch (ClassCastException e) { + return null; + } + } + + /** + * Delegates to {@link Map#containsKey}. Returns {@code false} on {@code + * ClassCastException} + */ + static boolean safeContainsKey(Map map, Object key) { + try { + return map.containsKey(key); + } catch (ClassCastException e) { + return false; + } + } + + /** + * Implements {@code Collection.contains} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + *

Note that {@code c} is the backing (delegate) collection, rather than + * the forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object that might be contained in {@code c} + * @return {@code true} if {@code c} contains {@code o} + */ + static boolean containsEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.contains(unmodifiableEntry((Entry) o)); + } + + /** + * Implements {@code Collection.remove} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + *

Note that {@code c} is backing (delegate) collection, rather than the + * forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object to remove from {@code c} + * @return {@code true} if {@code c} was changed + */ + static boolean removeEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.remove(unmodifiableEntry((Entry) o)); + } + + /** + * An implementation of {@link Map#equals}. + */ + static boolean equalsImpl(Map map, Object object) { + if (map == object) { + return true; + } + if (object instanceof Map) { + Map o = (Map) object; + return map.entrySet().equals(o.entrySet()); + } + return false; + } + + /** + * An implementation of {@link Map#hashCode}. + */ + static int hashCodeImpl(Map map) { + return Sets.hashCodeImpl(map.entrySet()); + } + + /** + * An implementation of {@link Map#toString}. + */ + static String toStringImpl(Map map) { + StringBuilder sb + = Collections2.newStringBuilderForCollection(map.size()).append('{'); + STANDARD_JOINER.appendTo(sb, map); + return sb.append('}').toString(); + } + + /** + * An implementation of {@link Map#putAll}. + */ + static void putAllImpl( + Map self, Map map) { + for (Map.Entry entry : map.entrySet()) { + self.put(entry.getKey(), entry.getValue()); + } + } + + /** + * An admittedly inefficient implementation of {@link Map#containsKey}. + */ + static boolean containsKeyImpl(Map map, @Nullable Object key) { + for (Entry entry : map.entrySet()) { + if (Objects.equal(entry.getKey(), key)) { + return true; + } + } + return false; + } + + /** + * An implementation of {@link Map#containsValue}. + */ + static boolean containsValueImpl(Map map, @Nullable Object value) { + for (Entry entry : map.entrySet()) { + if (Objects.equal(entry.getValue(), value)) { + return true; + } + } + return false; + } + + static Iterator keyIterator(Iterator> entryIterator) { + return new TransformedIterator, K>(entryIterator) { + @Override + K transform(Entry entry) { + return entry.getKey(); + } + }; + } + + abstract static class KeySet extends Sets.ImprovedAbstractSet { + abstract Map map(); + + @Override public Iterator iterator() { + return keyIterator(map().entrySet().iterator()); + } + + @Override public int size() { + return map().size(); + } + + @Override public boolean isEmpty() { + return map().isEmpty(); + } + + @Override public boolean contains(Object o) { + return map().containsKey(o); + } + + @Override public boolean remove(Object o) { + if (contains(o)) { + map().remove(o); + return true; + } + return false; + } + + @Override public void clear() { + map().clear(); + } + } + + @Nullable + static K keyOrNull(@Nullable Entry entry) { + return (entry == null) ? null : entry.getKey(); + } + + @GwtIncompatible("NavigableMap") + abstract static class NavigableKeySet extends KeySet implements NavigableSet { + @Override + abstract NavigableMap map(); + + @Override + public Comparator comparator() { + return map().comparator(); + } + + @Override + public K first() { + return map().firstKey(); + } + + @Override + public K last() { + return map().lastKey(); + } + + @Override + public K lower(K e) { + return map().lowerKey(e); + } + + @Override + public K floor(K e) { + return map().floorKey(e); + } + + @Override + public K ceiling(K e) { + return map().ceilingKey(e); + } + + @Override + public K higher(K e) { + return map().higherKey(e); + } + + @Override + public K pollFirst() { + return keyOrNull(map().pollFirstEntry()); + } + + @Override + public K pollLast() { + return keyOrNull(map().pollLastEntry()); + } + + @Override + public NavigableSet descendingSet() { + return map().descendingKeySet(); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public NavigableSet subSet( + K fromElement, + boolean fromInclusive, + K toElement, + boolean toInclusive) { + return map().subMap(fromElement, fromInclusive, toElement, toInclusive).navigableKeySet(); + } + + @Override + public NavigableSet headSet(K toElement, boolean inclusive) { + return map().headMap(toElement, inclusive).navigableKeySet(); + } + + @Override + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return map().tailMap(fromElement, inclusive).navigableKeySet(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public SortedSet headSet(K toElement) { + return headSet(toElement, false); + } + + @Override + public SortedSet tailSet(K fromElement) { + return tailSet(fromElement, true); + } + } + + static Iterator valueIterator(Iterator> entryIterator) { + return new TransformedIterator, V>(entryIterator) { + @Override + V transform(Entry entry) { + return entry.getValue(); + } + }; + } + + static UnmodifiableIterator valueIterator( + final UnmodifiableIterator> entryIterator) { + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public V next() { + return entryIterator.next().getValue(); + } + }; + } + + abstract static class Values extends AbstractCollection { + abstract Map map(); + + @Override public Iterator iterator() { + return valueIterator(map().entrySet().iterator()); + } + + @Override public boolean remove(Object o) { + try { + return super.remove(o); + } catch (UnsupportedOperationException e) { + for (Entry entry : map().entrySet()) { + if (Objects.equal(o, entry.getValue())) { + map().remove(entry.getKey()); + return true; + } + } + return false; + } + } + + @Override public boolean removeAll(Collection c) { + try { + return super.removeAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + Set toRemove = Sets.newHashSet(); + for (Entry entry : map().entrySet()) { + if (c.contains(entry.getValue())) { + toRemove.add(entry.getKey()); + } + } + return map().keySet().removeAll(toRemove); + } + } + + @Override public boolean retainAll(Collection c) { + try { + return super.retainAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + Set toRetain = Sets.newHashSet(); + for (Entry entry : map().entrySet()) { + if (c.contains(entry.getValue())) { + toRetain.add(entry.getKey()); + } + } + return map().keySet().retainAll(toRetain); + } + } + + @Override public int size() { + return map().size(); + } + + @Override public boolean isEmpty() { + return map().isEmpty(); + } + + @Override public boolean contains(@Nullable Object o) { + return map().containsValue(o); + } + + @Override public void clear() { + map().clear(); + } + } + + abstract static class EntrySet + extends Sets.ImprovedAbstractSet> { + abstract Map map(); + + @Override public int size() { + return map().size(); + } + + @Override public void clear() { + map().clear(); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + Object key = entry.getKey(); + V value = map().get(key); + return Objects.equal(value, entry.getValue()) + && (value != null || map().containsKey(key)); + } + return false; + } + + @Override public boolean isEmpty() { + return map().isEmpty(); + } + + @Override public boolean remove(Object o) { + if (contains(o)) { + Entry entry = (Entry) o; + return map().keySet().remove(entry.getKey()); + } + return false; + } + + @Override public boolean removeAll(Collection c) { + try { + return super.removeAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + // if the iterators don't support remove + boolean changed = true; + for (Object o : c) { + changed |= remove(o); + } + return changed; + } + } + + @Override public boolean retainAll(Collection c) { + try { + return super.retainAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + // if the iterators don't support remove + Set keys = Sets.newHashSetWithExpectedSize(c.size()); + for (Object o : c) { + if (contains(o)) { + Entry entry = (Entry) o; + keys.add(entry.getKey()); + } + } + return map().keySet().retainAll(keys); + } + } + } + + @GwtIncompatible("NavigableMap") + abstract static class DescendingMap extends ForwardingMap + implements NavigableMap { + + abstract NavigableMap forward(); + + @Override + protected final Map delegate() { + return forward(); + } + + private transient Comparator comparator; + + @SuppressWarnings("unchecked") + @Override + public Comparator comparator() { + Comparator result = comparator; + if (result == null) { + Comparator forwardCmp = forward().comparator(); + if (forwardCmp == null) { + forwardCmp = (Comparator) Ordering.natural(); + } + result = comparator = reverse(forwardCmp); + } + return result; + } + + // If we inline this, we get a javac error. + private static Ordering reverse(Comparator forward) { + return Ordering.from(forward).reverse(); + } + + @Override + public K firstKey() { + return forward().lastKey(); + } + + @Override + public K lastKey() { + return forward().firstKey(); + } + + @Override + public Entry lowerEntry(K key) { + return forward().higherEntry(key); + } + + @Override + public K lowerKey(K key) { + return forward().higherKey(key); + } + + @Override + public Entry floorEntry(K key) { + return forward().ceilingEntry(key); + } + + @Override + public K floorKey(K key) { + return forward().ceilingKey(key); + } + + @Override + public Entry ceilingEntry(K key) { + return forward().floorEntry(key); + } + + @Override + public K ceilingKey(K key) { + return forward().floorKey(key); + } + + @Override + public Entry higherEntry(K key) { + return forward().lowerEntry(key); + } + + @Override + public K higherKey(K key) { + return forward().lowerKey(key); + } + + @Override + public Entry firstEntry() { + return forward().lastEntry(); + } + + @Override + public Entry lastEntry() { + return forward().firstEntry(); + } + + @Override + public Entry pollFirstEntry() { + return forward().pollLastEntry(); + } + + @Override + public Entry pollLastEntry() { + return forward().pollFirstEntry(); + } + + @Override + public NavigableMap descendingMap() { + return forward(); + } + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + abstract Iterator> entryIterator(); + + Set> createEntrySet() { + return new EntrySet() { + + @Override + Map map() { + return DescendingMap.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + }; + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + private transient NavigableSet navigableKeySet; + + @Override + public NavigableSet navigableKeySet() { + NavigableSet result = navigableKeySet; + if (result == null) { + result = navigableKeySet = new NavigableKeySet() { + @Override + NavigableMap map() { + return DescendingMap.this; + } + }; + } + return result; + } + + @Override + public NavigableSet descendingKeySet() { + return forward().navigableKeySet(); + } + + @Override + public + NavigableMap + subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return forward().subMap(toKey, toInclusive, fromKey, fromInclusive).descendingMap(); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return forward().tailMap(toKey, inclusive).descendingMap(); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return forward().headMap(fromKey, inclusive).descendingMap(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public Collection values() { + return new Values() { + @Override + Map map() { + return DescendingMap.this; + } + }; + } + } +} diff --git a/guava/src/com/google/common/collect/MinMaxPriorityQueue.java b/guava/src/com/google/common/collect/MinMaxPriorityQueue.java new file mode 100644 index 0000000..f9c2d92 --- /dev/null +++ b/guava/src/com/google/common/collect/MinMaxPriorityQueue.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.math.IntMath; + +import java.util.AbstractQueue; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * A double-ended priority queue, which provides constant-time access to both + * its least element and its greatest element, as determined by the queue's + * specified comparator. If no comparator is given at construction time, the + * natural order of elements is used. + * + *

As a {@link Queue} it functions exactly as a {@link PriorityQueue}: its + * head element -- the implicit target of the methods {@link #peek()}, {@link + * #poll()} and {@link #remove()} -- is defined as the least element in + * the queue according to the queue's comparator. But unlike a regular priority + * queue, the methods {@link #peekLast}, {@link #pollLast} and + * {@link #removeLast} are also provided, to act on the greatest element + * in the queue instead. + * + *

A min-max priority queue can be configured with a maximum size. If so, + * each time the size of the queue exceeds that value, the queue automatically + * removes its greatest element according to its comparator (which might be the + * element that was just added). This is different from conventional bounded + * queues, which either block or reject new elements when full. + * + *

This implementation is based on the + * min-max heap + * developed by Atkinson, et al. Unlike many other double-ended priority queues, + * it stores elements in a single array, as compact as the traditional heap data + * structure used in {@link PriorityQueue}. + * + *

This class is not thread-safe, and does not accept null elements. + * + *

Performance notes: + * + *

    + *
  • The retrieval operations {@link #peek}, {@link #peekFirst}, {@link + * #peekLast}, {@link #element}, and {@link #size} are constant-time + *
  • The enqueing and dequeing operations ({@link #offer}, {@link #add}, and + * all the forms of {@link #poll} and {@link #remove()}) run in {@code + * O(log n) time} + *
  • The {@link #remove(Object)} and {@link #contains} operations require + * linear ({@code O(n)}) time + *
  • If you only access one end of the queue, and don't use a maximum size, + * this class is functionally equivalent to {@link PriorityQueue}, but + * significantly slower. + *
+ * + * @author Sverre Sundsdal + * @author Torbjorn Gannholm + * @since 8.0 + */ +// TODO(kevinb): GWT compatibility +@Beta +public final class MinMaxPriorityQueue extends AbstractQueue { + + /** + * Creates a new min-max priority queue with default settings: natural order, + * no maximum size, no initial contents, and an initial expected size of 11. + */ + public static > MinMaxPriorityQueue create() { + return new Builder(Ordering.natural()).create(); + } + + /** + * Creates a new min-max priority queue using natural order, no maximum size, + * and initially containing the given elements. + */ + public static > MinMaxPriorityQueue create( + Iterable initialContents) { + return new Builder(Ordering.natural()).create(initialContents); + } + + /** + * Creates and returns a new builder, configured to build {@code + * MinMaxPriorityQueue} instances that use {@code comparator} to determine the + * least and greatest elements. + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Creates and returns a new builder, configured to build {@code + * MinMaxPriorityQueue} instances sized appropriately to hold {@code + * expectedSize} elements. + */ + public static Builder expectedSize(int expectedSize) { + return new Builder(Ordering.natural()) + .expectedSize(expectedSize); + } + + /** + * Creates and returns a new builder, configured to build {@code + * MinMaxPriorityQueue} instances that are limited to {@code maximumSize} + * elements. Each time a queue grows beyond this bound, it immediately + * removes its greatest element (according to its comparator), which might be + * the element that was just added. + */ + public static Builder maximumSize(int maximumSize) { + return new Builder(Ordering.natural()) + .maximumSize(maximumSize); + } + + /** + * The builder class used in creation of min-max priority queues. Instead of + * constructing one directly, use {@link + * MinMaxPriorityQueue#orderedBy(Comparator)}, {@link + * MinMaxPriorityQueue#expectedSize(int)} or {@link + * MinMaxPriorityQueue#maximumSize(int)}. + * + * @param the upper bound on the eventual type that can be produced by + * this builder (for example, a {@code Builder} can produce a + * {@code Queue} or {@code Queue} but not a {@code + * Queue}). + * @since 8.0 + */ + @Beta + public static final class Builder { + /* + * TODO(kevinb): when the dust settles, see if we still need this or can + * just default to DEFAULT_CAPACITY. + */ + private static final int UNSET_EXPECTED_SIZE = -1; + + private final Comparator comparator; + private int expectedSize = UNSET_EXPECTED_SIZE; + private int maximumSize = Integer.MAX_VALUE; + + private Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Configures this builder to build min-max priority queues with an initial + * expected size of {@code expectedSize}. + */ + public Builder expectedSize(int expectedSize) { + checkArgument(expectedSize >= 0); + this.expectedSize = expectedSize; + return this; + } + + /** + * Configures this builder to build {@code MinMaxPriorityQueue} instances + * that are limited to {@code maximumSize} elements. Each time a queue grows + * beyond this bound, it immediately removes its greatest element (according + * to its comparator), which might be the element that was just added. + */ + public Builder maximumSize(int maximumSize) { + checkArgument(maximumSize > 0); + this.maximumSize = maximumSize; + return this; + } + + /** + * Builds a new min-max priority queue using the previously specified + * options, and having no initial contents. + */ + public MinMaxPriorityQueue create() { + return create(Collections.emptySet()); + } + + /** + * Builds a new min-max priority queue using the previously specified + * options, and having the given initial elements. + */ + public MinMaxPriorityQueue create( + Iterable initialContents) { + MinMaxPriorityQueue queue = new MinMaxPriorityQueue( + this, initialQueueSize(expectedSize, maximumSize, initialContents)); + for (T element : initialContents) { + queue.offer(element); + } + return queue; + } + + @SuppressWarnings("unchecked") // safe "contravariant cast" + private Ordering ordering() { + return Ordering.from((Comparator) comparator); + } + } + + private final Heap minHeap; + private final Heap maxHeap; + @VisibleForTesting final int maximumSize; + private Object[] queue; + private int size; + private int modCount; + + private MinMaxPriorityQueue(Builder builder, int queueSize) { + Ordering ordering = builder.ordering(); + this.minHeap = new Heap(ordering); + this.maxHeap = new Heap(ordering.reverse()); + minHeap.otherHeap = maxHeap; + maxHeap.otherHeap = minHeap; + + this.maximumSize = builder.maximumSize; + // TODO(kevinb): pad? + this.queue = new Object[queueSize]; + } + + @Override public int size() { + return size; + } + + /** + * Adds the given element to this queue. If this queue has a maximum size, + * after adding {@code element} the queue will automatically evict its + * greatest element (according to its comparator), which may be {@code + * element} itself. + * + * @return {@code true} always + */ + @Override public boolean add(E element) { + offer(element); + return true; + } + + @Override public boolean addAll(Collection newElements) { + boolean modified = false; + for (E element : newElements) { + offer(element); + modified = true; + } + return modified; + } + + /** + * Adds the given element to this queue. If this queue has a maximum size, + * after adding {@code element} the queue will automatically evict its + * greatest element (according to its comparator), which may be {@code + * element} itself. + */ + @Override public boolean offer(E element) { + checkNotNull(element); + modCount++; + int insertIndex = size++; + + growIfNeeded(); + + // Adds the element to the end of the heap and bubbles it up to the correct + // position. + heapForIndex(insertIndex).bubbleUp(insertIndex, element); + return size <= maximumSize || pollLast() != element; + } + + @Override public E poll() { + return isEmpty() ? null : removeAndGet(0); + } + + @SuppressWarnings("unchecked") // we must carefully only allow Es to get in + E elementData(int index) { + return (E) queue[index]; + } + + @Override public E peek() { + return isEmpty() ? null : elementData(0); + } + + /** + * Returns the index of the max element. + */ + private int getMaxElementIndex() { + switch (size) { + case 1: + return 0; // The lone element in the queue is the maximum. + case 2: + return 1; // The lone element in the maxHeap is the maximum. + default: + // The max element must sit on the first level of the maxHeap. It is + // actually the *lesser* of the two from the maxHeap's perspective. + return (maxHeap.compareElements(1, 2) <= 0) ? 1 : 2; + } + } + + /** + * Removes and returns the least element of this queue, or returns {@code + * null} if the queue is empty. + */ + public E pollFirst() { + return poll(); + } + + /** + * Removes and returns the least element of this queue. + * + * @throws NoSuchElementException if the queue is empty + */ + public E removeFirst() { + return remove(); + } + + /** + * Retrieves, but does not remove, the least element of this queue, or returns + * {@code null} if the queue is empty. + */ + public E peekFirst() { + return peek(); + } + + /** + * Removes and returns the greatest element of this queue, or returns {@code + * null} if the queue is empty. + */ + public E pollLast() { + return isEmpty() ? null : removeAndGet(getMaxElementIndex()); + } + + /** + * Removes and returns the greatest element of this queue. + * + * @throws NoSuchElementException if the queue is empty + */ + public E removeLast() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return removeAndGet(getMaxElementIndex()); + } + + /** + * Retrieves, but does not remove, the greatest element of this queue, or + * returns {@code null} if the queue is empty. + */ + public E peekLast() { + return isEmpty() ? null : elementData(getMaxElementIndex()); + } + + /** + * Removes the element at position {@code index}. + * + *

Normally this method leaves the elements at up to {@code index - 1}, + * inclusive, untouched. Under these circumstances, it returns {@code null}. + * + *

Occasionally, in order to maintain the heap invariant, it must swap a + * later element of the list with one before {@code index}. Under these + * circumstances it returns a pair of elements as a {@link MoveDesc}. The + * first one is the element that was previously at the end of the heap and is + * now at some position before {@code index}. The second element is the one + * that was swapped down to replace the element at {@code index}. This fact is + * used by iterator.remove so as to visit elements during a traversal once and + * only once. + */ + @VisibleForTesting MoveDesc removeAt(int index) { + checkPositionIndex(index, size); + modCount++; + size--; + if (size == index) { + queue[size] = null; + return null; + } + E actualLastElement = elementData(size); + int lastElementAt = heapForIndex(size) + .getCorrectLastElement(actualLastElement); + E toTrickle = elementData(size); + queue[size] = null; + MoveDesc changes = fillHole(index, toTrickle); + if (lastElementAt < index) { + // Last element is moved to before index, swapped with trickled element. + if (changes == null) { + // The trickled element is still after index. + return new MoveDesc(actualLastElement, toTrickle); + } else { + // The trickled element is back before index, but the replaced element + // has now been moved after index. + return new MoveDesc(actualLastElement, changes.replaced); + } + } + // Trickled element was after index to begin with, no adjustment needed. + return changes; + } + + private MoveDesc fillHole(int index, E toTrickle) { + Heap heap = heapForIndex(index); + // We consider elementData(index) a "hole", and we want to fill it + // with the last element of the heap, toTrickle. + // Since the last element of the heap is from the bottom level, we + // optimistically fill index position with elements from lower levels, + // moving the hole down. In most cases this reduces the number of + // comparisons with toTrickle, but in some cases we will need to bubble it + // all the way up again. + int vacated = heap.fillHoleAt(index); + // Try to see if toTrickle can be bubbled up min levels. + int bubbledTo = heap.bubbleUpAlternatingLevels(vacated, toTrickle); + if (bubbledTo == vacated) { + // Could not bubble toTrickle up min levels, try moving + // it from min level to max level (or max to min level) and bubble up + // there. + return heap.tryCrossOverAndBubbleUp(index, vacated, toTrickle); + } else { + return (bubbledTo < index) + ? new MoveDesc(toTrickle, elementData(index)) + : null; + } + } + + // Returned from removeAt() to iterator.remove() + static class MoveDesc { + final E toTrickle; + final E replaced; + + MoveDesc(E toTrickle, E replaced) { + this.toTrickle = toTrickle; + this.replaced = replaced; + } + } + + /** + * Removes and returns the value at {@code index}. + */ + private E removeAndGet(int index) { + E value = elementData(index); + removeAt(index); + return value; + } + + private Heap heapForIndex(int i) { + return isEvenLevel(i) ? minHeap : maxHeap; + } + + private static final int EVEN_POWERS_OF_TWO = 0x55555555; + private static final int ODD_POWERS_OF_TWO = 0xaaaaaaaa; + + @VisibleForTesting static boolean isEvenLevel(int index) { + int oneBased = index + 1; + checkState(oneBased > 0, "negative index"); + return (oneBased & EVEN_POWERS_OF_TWO) > (oneBased & ODD_POWERS_OF_TWO); + } + + /** + * Returns {@code true} if the MinMax heap structure holds. This is only used + * in testing. + * + * TODO(kevinb): move to the test class? + */ + @VisibleForTesting boolean isIntact() { + for (int i = 1; i < size; i++) { + if (!heapForIndex(i).verifyIndex(i)) { + return false; + } + } + return true; + } + + /** + * Each instance of MinMaxPriortyQueue encapsulates two instances of Heap: + * a min-heap and a max-heap. Conceptually, these might each have their own + * array for storage, but for efficiency's sake they are stored interleaved on + * alternate heap levels in the same array (MMPQ.queue). + */ + private class Heap { + final Ordering ordering; + Heap otherHeap; + + Heap(Ordering ordering) { + this.ordering = ordering; + } + + int compareElements(int a, int b) { + return ordering.compare(elementData(a), elementData(b)); + } + + /** + * Tries to move {@code toTrickle} from a min to a max level and + * bubble up there. If it moved before {@code removeIndex} this method + * returns a pair as described in {@link #removeAt}. + */ + MoveDesc tryCrossOverAndBubbleUp( + int removeIndex, int vacated, E toTrickle) { + int crossOver = crossOver(vacated, toTrickle); + if (crossOver == vacated) { + return null; + } + // Successfully crossed over from min to max. + // Bubble up max levels. + E parent; + // If toTrickle is moved up to a parent of removeIndex, the parent is + // placed in removeIndex position. We must return that to the iterator so + // that it knows to skip it. + if (crossOver < removeIndex) { + // We crossed over to the parent level in crossOver, so the parent + // has already been moved. + parent = elementData(removeIndex); + } else { + parent = elementData(getParentIndex(removeIndex)); + } + // bubble it up the opposite heap + if (otherHeap.bubbleUpAlternatingLevels(crossOver, toTrickle) + < removeIndex) { + return new MoveDesc(toTrickle, parent); + } else { + return null; + } + } + + /** + * Bubbles a value from {@code index} up the appropriate heap if required. + */ + void bubbleUp(int index, E x) { + int crossOver = crossOverUp(index, x); + + Heap heap; + if (crossOver == index) { + heap = this; + } else { + index = crossOver; + heap = otherHeap; + } + heap.bubbleUpAlternatingLevels(index, x); + } + + /** + * Bubbles a value from {@code index} up the levels of this heap, and + * returns the index the element ended up at. + */ + int bubbleUpAlternatingLevels(int index, E x) { + while (index > 2) { + int grandParentIndex = getGrandparentIndex(index); + E e = elementData(grandParentIndex); + if (ordering.compare(e, x) <= 0) { + break; + } + queue[index] = e; + index = grandParentIndex; + } + queue[index] = x; + return index; + } + + /** + * Returns the index of minimum value between {@code index} and + * {@code index + len}, or {@code -1} if {@code index} is greater than + * {@code size}. + */ + int findMin(int index, int len) { + if (index >= size) { + return -1; + } + checkState(index > 0); + int limit = Math.min(index, size - len) + len; + int minIndex = index; + for (int i = index + 1; i < limit; i++) { + if (compareElements(i, minIndex) < 0) { + minIndex = i; + } + } + return minIndex; + } + + /** + * Returns the minimum child or {@code -1} if no child exists. + */ + int findMinChild(int index) { + return findMin(getLeftChildIndex(index), 2); + } + + /** + * Returns the minimum grand child or -1 if no grand child exists. + */ + int findMinGrandChild(int index) { + int leftChildIndex = getLeftChildIndex(index); + if (leftChildIndex < 0) { + return -1; + } + return findMin(getLeftChildIndex(leftChildIndex), 4); + } + + /** + * Moves an element one level up from a min level to a max level + * (or vice versa). + * Returns the new position of the element. + */ + int crossOverUp(int index, E x) { + if (index == 0) { + queue[0] = x; + return 0; + } + int parentIndex = getParentIndex(index); + E parentElement = elementData(parentIndex); + if (parentIndex != 0) { + // This is a guard for the case of the childless uncle. + // Since the end of the array is actually the middle of the heap, + // a smaller childless uncle can become a child of x when we + // bubble up alternate levels, violating the invariant. + int grandparentIndex = getParentIndex(parentIndex); + int uncleIndex = getRightChildIndex(grandparentIndex); + if (uncleIndex != parentIndex + && getLeftChildIndex(uncleIndex) >= size) { + E uncleElement = elementData(uncleIndex); + if (ordering.compare(uncleElement, parentElement) < 0) { + parentIndex = uncleIndex; + parentElement = uncleElement; + } + } + } + if (ordering.compare(parentElement, x) < 0) { + queue[index] = parentElement; + queue[parentIndex] = x; + return parentIndex; + } + queue[index] = x; + return index; + } + + /** + * Returns the conceptually correct last element of the heap. + * + *

Since the last element of the array is actually in the + * middle of the sorted structure, a childless uncle node could be + * smaller, which would corrupt the invariant if this element + * becomes the new parent of the uncle. In that case, we first + * switch the last element with its uncle, before returning. + */ + int getCorrectLastElement(E actualLastElement) { + int parentIndex = getParentIndex(size); + if (parentIndex != 0) { + int grandparentIndex = getParentIndex(parentIndex); + int uncleIndex = getRightChildIndex(grandparentIndex); + if (uncleIndex != parentIndex + && getLeftChildIndex(uncleIndex) >= size) { + E uncleElement = elementData(uncleIndex); + if (ordering.compare(uncleElement, actualLastElement) < 0) { + queue[uncleIndex] = actualLastElement; + queue[size] = uncleElement; + return uncleIndex; + } + } + } + return size; + } + + /** + * Crosses an element over to the opposite heap by moving it one level down + * (or up if there are no elements below it). + * + * Returns the new position of the element. + */ + int crossOver(int index, E x) { + int minChildIndex = findMinChild(index); + // TODO(kevinb): split the && into two if's and move crossOverUp so it's + // only called when there's no child. + if ((minChildIndex > 0) + && (ordering.compare(elementData(minChildIndex), x) < 0)) { + queue[index] = elementData(minChildIndex); + queue[minChildIndex] = x; + return minChildIndex; + } + return crossOverUp(index, x); + } + + /** + * Fills the hole at {@code index} by moving in the least of its + * grandchildren to this position, then recursively filling the new hole + * created. + * + * @return the position of the new hole (where the lowest grandchild moved + * from, that had no grandchild to replace it) + */ + int fillHoleAt(int index) { + int minGrandchildIndex; + while ((minGrandchildIndex = findMinGrandChild(index)) > 0) { + queue[index] = elementData(minGrandchildIndex); + index = minGrandchildIndex; + } + return index; + } + + private boolean verifyIndex(int i) { + if ((getLeftChildIndex(i) < size) + && (compareElements(i, getLeftChildIndex(i)) > 0)) { + return false; + } + if ((getRightChildIndex(i) < size) + && (compareElements(i, getRightChildIndex(i)) > 0)) { + return false; + } + if ((i > 0) && (compareElements(i, getParentIndex(i)) > 0)) { + return false; + } + if ((i > 2) && (compareElements(getGrandparentIndex(i), i) > 0)) { + return false; + } + return true; + } + + // These would be static if inner classes could have static members. + + private int getLeftChildIndex(int i) { + return i * 2 + 1; + } + + private int getRightChildIndex(int i) { + return i * 2 + 2; + } + + private int getParentIndex(int i) { + return (i - 1) / 2; + } + + private int getGrandparentIndex(int i) { + return getParentIndex(getParentIndex(i)); // (i - 3) / 4 + } + } + + /** + * Iterates the elements of the queue in no particular order. + * + * If the underlying queue is modified during iteration an exception will be + * thrown. + */ + private class QueueIterator implements Iterator { + private int cursor = -1; + private int expectedModCount = modCount; + private Queue forgetMeNot; + private List skipMe; + private E lastFromForgetMeNot; + private boolean canRemove; + + @Override public boolean hasNext() { + checkModCount(); + return (nextNotInSkipMe(cursor + 1) < size()) + || ((forgetMeNot != null) && !forgetMeNot.isEmpty()); + } + + @Override public E next() { + checkModCount(); + int tempCursor = nextNotInSkipMe(cursor + 1); + if (tempCursor < size()) { + cursor = tempCursor; + canRemove = true; + return elementData(cursor); + } else if (forgetMeNot != null) { + cursor = size(); + lastFromForgetMeNot = forgetMeNot.poll(); + if (lastFromForgetMeNot != null) { + canRemove = true; + return lastFromForgetMeNot; + } + } + throw new NoSuchElementException( + "iterator moved past last element in queue."); + } + + @Override public void remove() { + checkState(canRemove, + "no calls to remove() since the last call to next()"); + checkModCount(); + canRemove = false; + expectedModCount++; + if (cursor < size()) { + MoveDesc moved = removeAt(cursor); + if (moved != null) { + if (forgetMeNot == null) { + forgetMeNot = new ArrayDeque(); + skipMe = new ArrayList(3); + } + forgetMeNot.add(moved.toTrickle); + skipMe.add(moved.replaced); + } + cursor--; + } else { // we must have set lastFromForgetMeNot in next() + checkState(removeExact(lastFromForgetMeNot)); + lastFromForgetMeNot = null; + } + } + + // Finds only this exact instance, not others that are equals() + private boolean containsExact(Iterable elements, E target) { + for (E element : elements) { + if (element == target) { + return true; + } + } + return false; + } + + // Removes only this exact instance, not others that are equals() + boolean removeExact(Object target) { + for (int i = 0; i < size; i++) { + if (queue[i] == target) { + removeAt(i); + return true; + } + } + return false; + } + + void checkModCount() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * Returns the index of the first element after {@code c} that is not in + * {@code skipMe} and returns {@code size()} if there is no such element. + */ + private int nextNotInSkipMe(int c) { + if (skipMe != null) { + while (c < size() && containsExact(skipMe, elementData(c))) { + c++; + } + } + return c; + } + } + + /** + * Returns an iterator over the elements contained in this collection, + * in no particular order. + * + *

The iterator is fail-fast: If the MinMaxPriorityQueue is modified + * at any time after the iterator is created, in any way except through the + * iterator's own remove method, the iterator will generally throw a + * {@link ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + * @return an iterator over the elements contained in this collection + */ + @Override public Iterator iterator() { + return new QueueIterator(); + } + + @Override public void clear() { + for (int i = 0; i < size; i++) { + queue[i] = null; + } + size = 0; + } + + @Override public Object[] toArray() { + Object[] copyTo = new Object[size]; + System.arraycopy(queue, 0, copyTo, 0, size); + return copyTo; + } + + /** + * Returns the comparator used to order the elements in this queue. Obeys the + * general contract of {@link PriorityQueue#comparator}, but returns {@link + * Ordering#natural} instead of {@code null} to indicate natural ordering. + */ + public Comparator comparator() { + return minHeap.ordering; + } + + @VisibleForTesting int capacity() { + return queue.length; + } + + // Size/capacity-related methods + + private static final int DEFAULT_CAPACITY = 11; + + @VisibleForTesting static int initialQueueSize(int configuredExpectedSize, + int maximumSize, Iterable initialContents) { + // Start with what they said, if they said it, otherwise DEFAULT_CAPACITY + int result = (configuredExpectedSize == Builder.UNSET_EXPECTED_SIZE) + ? DEFAULT_CAPACITY + : configuredExpectedSize; + + // Enlarge to contain initial contents + if (initialContents instanceof Collection) { + int initialSize = ((Collection) initialContents).size(); + result = Math.max(result, initialSize); + } + + // Now cap it at maxSize + 1 + return capAtMaximumSize(result, maximumSize); + } + + private void growIfNeeded() { + if (size > queue.length) { + int newCapacity = calculateNewCapacity(); + Object[] newQueue = new Object[newCapacity]; + System.arraycopy(queue, 0, newQueue, 0, queue.length); + queue = newQueue; + } + } + + /** Returns ~2x the old capacity if small; ~1.5x otherwise. */ + private int calculateNewCapacity() { + int oldCapacity = queue.length; + int newCapacity = (oldCapacity < 64) + ? (oldCapacity + 1) * 2 + : IntMath.checkedMultiply(oldCapacity / 2, 3); + return capAtMaximumSize(newCapacity, maximumSize); + } + + /** There's no reason for the queueSize to ever be more than maxSize + 1 */ + private static int capAtMaximumSize(int queueSize, int maximumSize) { + return Math.min(queueSize - 1, maximumSize) + 1; // don't overflow + } +} diff --git a/guava/src/com/google/common/collect/Multimap.java b/guava/src/com/google/common/collect/Multimap.java new file mode 100644 index 0000000..7310842 --- /dev/null +++ b/guava/src/com/google/common/collect/Multimap.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A collection that maps keys to values, similar to {@link Map}, but in which + * each key may be associated with multiple values. You can visualize the + * contents of a multimap either as a map from keys to collections of values: + * + *

    + *
  • a → 1, 2 + *
  • b → 3 + *
+ * + * ... or as a single "flattened" collection of key-value pairs: + * + *
    + *
  • a → 1 + *
  • a → 2 + *
  • b → 3 + *
+ * + *

Important: although the first interpretation resembles how most + * multimaps are implemented, the design of the {@code Multimap} API is + * based on the second form. So, using the multimap shown above as an + * example, the {@link #size} is {@code 3}, not {@code 2}, and the {@link + * #values} collection is {@code [1, 2, 3]}, not {@code [[1, 2], [3]]}. For + * those times when the first style is more useful, use the multimap's {@link + * #asMap} view. + * + *

Example

+ * + *

The following code:

   {@code
+ *
+ *   ListMultimap multimap = ArrayListMultimap.create();
+ *   for (President pres : US_PRESIDENTS_IN_ORDER) {
+ *     multimap.put(pres.firstName(), pres.lastName());
+ *   }
+ *   for (String firstName : multimap.keySet()) {
+ *     List lastNames = multimap.get(firstName);
+ *     out.println(firstName + ": " + lastNames);
+ *   }}
+ * + * ... produces output such as:
   {@code
+ *
+ *   Zachary: [Taylor]
+ *   John: [Adams, Adams, Tyler, Kennedy]
+ *   George: [Washington, Bush, Bush]
+ *   Grover: [Cleveland]
+ *   ...}
+ * + *

Views

+ * + *

Much of the power of the multimap API comes from the view + * collections it provides. These always reflect the latest state of the + * multimap itself. When they support modification, the changes are + * write-through (they automatically update the backing multimap). These + * view collections are: + * + *

    + *
  • {@link #asMap}, mentioned above
  • + *
  • {@link #keys}, {@link #keySet}, {@link #values}, {@link #entries}, which + * are similar to the corresponding view collections of {@link Map} + *
  • and, notably, even the collection returned by {@link #get get(key)} is an + * active view of the values corresponding to {@code key} + *
+ * + *

The collections returned by the {@link #replaceValues replaceValues} and + * {@link #removeAll removeAll} methods, which contain values that have just + * been removed from the multimap, are naturally not views. + * + *

Subinterfaces

+ * + *

Instead of using the {@code Multimap} interface directly, prefer the + * subinterfaces {@link ListMultimap} and {@link SetMultimap}. These take their + * names from the fact that the collections they return from {@code get} behave + * like (and, of course, implement) {@link List} and {@link Set}, respectively. + * + *

For example, the "presidents" code snippet above used a {@code + * ListMultimap}; if it had used a {@code SetMultimap} instead, two presidents + * would have vanished, and last names might or might not appear in + * chronological order. + * + *

Uses

+ * + *

Multimaps are commonly used anywhere a {@code Map>} would + * otherwise have appeared. The advantages include: + * + *

    + *
  • There is no need to populate an empty collection before adding an entry + * with {@link #put put}. + *
  • {@code get} never returns {@code null}, only an empty collection. + *
  • It will not retain empty collections after the last value for a key is + * removed. As a result, {@link #containsKey} behaves logically, and the + * multimap won't leak memory. + *
  • The total entry count is available as {@link #size}. + *
  • Many complex operations become easier; for example, {@code + * Collections.min(multimap.values())} finds the smallest value across all + * keys. + *
+ * + *

Implementations

+ * + *

As always, prefer the immutable implementations, {@link + * ImmutableListMultimap} and {@link ImmutableSetMultimap}. General-purpose + * mutable implementations are listed above under "All Known Implementing + * Classes". You can also create a custom multimap, backed by any {@code + * Map} and {@link Collection} types, using the {@link Multimaps#newMultimap + * Multimaps.newMultimap} family of methods. Finally, another popular way to + * obtain a multimap is using {@link Multimaps#index Multimaps.index}. See + * the {@link Multimaps} class for these and other static utilities related + * to multimaps. + * + *

Other Notes

+ * + *

All methods that modify the multimap are optional. The view collections + * returned by the multimap may or may not be modifiable. Any modification + * method that is not supported will throw {@link + * UnsupportedOperationException}. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface Multimap { + // Query Operations + + /** Returns the number of key-value pairs in the multimap. */ + int size(); + + /** Returns {@code true} if the multimap contains no key-value pairs. */ + boolean isEmpty(); + + /** + * Returns {@code true} if the multimap contains any values for the specified + * key. + * + * @param key key to search for in multimap + */ + boolean containsKey(@Nullable Object key); + + /** + * Returns {@code true} if the multimap contains the specified value for any + * key. + * + * @param value value to search for in multimap + */ + boolean containsValue(@Nullable Object value); + + /** + * Returns {@code true} if the multimap contains the specified key-value pair. + * + * @param key key to search for in multimap + * @param value value to search for in multimap + */ + boolean containsEntry(@Nullable Object key, @Nullable Object value); + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + *

Some multimap implementations allow duplicate key-value pairs, in which + * case {@code put} always adds a new key-value pair and increases the + * multimap size by 1. Other implementations prohibit duplicates, and storing + * a key-value pair that's already in the multimap has no effect. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair and + * doesn't allow duplicates + */ + boolean put(@Nullable K key, @Nullable V value); + + /** + * Removes a single key-value pair from the multimap. + * + * @param key key of entry to remove from the multimap + * @param value value of entry to remove the multimap + * @return {@code true} if the multimap changed + */ + boolean remove(@Nullable Object key, @Nullable Object value); + + // Bulk Operations + + /** + * Stores a collection of values with the same key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(@Nullable K key, Iterable values); + + /** + * Copies all of another multimap's key-value pairs into this multimap. The + * order in which the mappings are added is determined by + * {@code multimap.entries()}. + * + * @param multimap mappings to store in this multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(Multimap multimap); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return the collection of replaced values, or an empty collection if no + * values were previously associated with the key. The collection + * may be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection replaceValues(@Nullable K key, Iterable values); + + /** + * Removes all values associated with a given key. + * + * @param key key of entries to remove from the multimap + * @return the collection of removed values, or an empty collection if no + * values were associated with the provided key. The collection + * may be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection removeAll(@Nullable Object key); + + /** + * Removes all key-value pairs from the multimap. + */ + void clear(); + + // Views + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + * @param key key to search for in multimap + * @return the collection of values that the key maps to + */ + Collection get(@Nullable K key); + + /** + * Returns the set of all keys, each appearing once in the returned set. + * Changes to the returned set will update the underlying multimap, and vice + * versa. + * + * @return the collection of distinct keys + */ + Set keySet(); + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times of key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Changes to the returned multiset will + * update the underlying multimap, and vice versa. + * + * @return a multiset with keys corresponding to the distinct keys of the + * multimap and frequencies corresponding to the number of values that + * each key maps to + */ + Multiset keys(); + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + * @return collection of values, which may include the same value multiple + * times if it occurs in multiple mappings + */ + Collection values(); + + /** + * Returns a collection of all key-value pairs. Changes to the returned + * collection will update the underlying multimap, and vice versa. The entries + * collection does not support the {@code add} or {@code addAll} operations. + * + * @return collection of map entries consisting of key-value pairs + */ + Collection> entries(); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, will + * update the underlying multimap. The map does not support {@code setValue()} + * on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code + * asMap().get(Object)} has the same behavior as {@link #get}, returning a + * live collection. When passed a key that is not present, however, {@code + * asMap().get(Object)} returns {@code null} instead of an empty collection. + * + * @return a map view from a key to its collection of values + */ + Map> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two + * multimaps are equal when their map views, as returned by {@link #asMap}, + * are also equal. + * + *

In general, two multimaps with identical key-value mappings may or may + * not be equal, depending on the implementation. For example, two + * {@link SetMultimap} instances with the same key-value mappings are equal, + * but equality of two {@link ListMultimap} instances depends on the ordering + * of the values for each key. + * + *

A non-empty {@link SetMultimap} cannot be equal to a non-empty + * {@link ListMultimap}, since their {@link #asMap} views contain unequal + * collections as values. However, any two empty multimaps are equal, because + * they both have empty {@link #asMap} views. + */ + @Override + boolean equals(@Nullable Object obj); + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + @Override + int hashCode(); +} diff --git a/guava/src/com/google/common/collect/Multimaps.java b/guava/src/com/google/common/collect/Multimaps.java new file mode 100644 index 0000000..1eb7fa8 --- /dev/null +++ b/guava/src/com/google/common/collect/Multimaps.java @@ -0,0 +1,2695 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Joiner.MapJoiner; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.collect.Collections2.TransformedCollection; +import com.google.common.collect.Maps.EntryTransformer; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Provides static methods acting on or generating a {@code Multimap}. + * + *

See the Guava User Guide article on + * {@code Multimaps}. + * + * @author Jared Levy + * @author Robert Konigsberg + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Multimaps { + private Multimaps() {} + + /** + * Creates a new {@code Multimap} that uses the provided map and factory. It + * can generate a multimap based on arbitrary {@link Map} and + * {@link Collection} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * collections generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedMultimap}. + * + *

Call this method only when the simpler methods + * {@link ArrayListMultimap#create()}, {@link HashMultimap#create()}, + * {@link LinkedHashMultimap#create()}, {@link LinkedListMultimap#create()}, + * {@link TreeMultimap#create()}, and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the collections returned by {@code factory}. Those objects should not be + * manually updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty collections that will each hold all + * values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static Multimap newMultimap(Map> map, + final Supplier> factory) { + return new CustomMultimap(map, factory); + } + + private static class CustomMultimap extends AbstractMultimap { + transient Supplier> factory; + + CustomMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Collection createCollection() { + return factory.get(); + } + + // can't use Serialization writeMultimap and populateMultimap methods since + // there's no way to generate the empty backing map. + + /** @serialData the factory and the backing map */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible("java serialization not supported") + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code ListMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link List} + * classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. The multimap's {@code get}, {@code + * removeAll}, and {@code replaceValues} methods return {@code RandomAccess} + * lists if the factory does. However, the multimap's {@code get} method + * returns instances of a different class than does {@code factory.get()}. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * lists generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedListMultimap}. + * + *

Call this method only when the simpler methods + * {@link ArrayListMultimap#create()} and {@link LinkedListMultimap#create()} + * won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the lists returned by {@code factory}. Those objects should not be manually + * updated, they should be empty when provided, and they should not use soft, + * weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty lists that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static ListMultimap newListMultimap( + Map> map, final Supplier> factory) { + return new CustomListMultimap(map, factory); + } + + private static class CustomListMultimap + extends AbstractListMultimap { + transient Supplier> factory; + + CustomListMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected List createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible("java serialization not supported") + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link Set} + * classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSetMultimap}. + * + *

Call this method only when the simpler methods + * {@link HashMultimap#create()}, {@link LinkedHashMultimap#create()}, + * {@link TreeMultimap#create()}, and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the sets returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty sets that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SetMultimap newSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSetMultimap(map, factory); + } + + private static class CustomSetMultimap + extends AbstractSetMultimap { + transient Supplier> factory; + + CustomSetMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Set createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible("not needed in emulated source") + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SortedSetMultimap} that uses the provided map and + * factory. It can generate a multimap based on arbitrary {@link Map} and + * {@link SortedSet} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSortedSetMultimap}. + * + *

Call this method only when the simpler methods + * {@link TreeMultimap#create()} and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the sets returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty sorted sets that will each hold + * all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SortedSetMultimap newSortedSetMultimap( + Map> map, + final Supplier> factory) { + return new CustomSortedSetMultimap(map, factory); + } + + private static class CustomSortedSetMultimap + extends AbstractSortedSetMultimap { + transient Supplier> factory; + transient Comparator valueComparator; + + CustomSortedSetMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + valueComparator = factory.get().comparator(); + } + + @Override protected SortedSet createCollection() { + return factory.get(); + } + + @Override public Comparator valueComparator() { + return valueComparator; + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + valueComparator = factory.get().comparator(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible("not needed in emulated source") + private static final long serialVersionUID = 0; + } + + /** + * Copies each key-value mapping in {@code source} into {@code dest}, with + * its key and value reversed. + * + *

If {@code source} is an {@link ImmutableMultimap}, consider using + * {@link ImmutableMultimap#inverse} instead. + * + * @param source any multimap + * @param dest the multimap to copy into; usually empty + * @return {@code dest} + */ + public static > M invertFrom( + Multimap source, M dest) { + checkNotNull(dest); + for (Map.Entry entry : source.entries()) { + dest.put(entry.getValue(), entry.getKey()); + } + return dest; + } + + /** + * Returns a synchronized (thread-safe) multimap backed by the specified + * multimap. In order to guarantee serial access, it is critical that + * all access to the backing multimap is accomplished through the + * returned multimap. + * + *

It is imperative that the user manually synchronize on the returned + * multimap when accessing any of its collection views:

   {@code
+   *
+   *   Multimap multimap = Multimaps.synchronizedMultimap(
+   *       HashMultimap.create());
+   *   ...
+   *   Collection values = multimap.get(key);  // Needn't be in synchronized block
+   *   ...
+   *   synchronized (multimap) {  // Synchronizing on multimap, not values!
+   *     Iterator i = values.iterator(); // Must be in synchronized block
+   *     while (i.hasNext()) {
+   *       foo(i.next());
+   *     }
+   *   }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that aren't + * synchronized. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static Multimap synchronizedMultimap( + Multimap multimap) { + return Synchronized.multimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified multimap. Query operations on + * the returned multimap "read through" to the specified multimap, and + * attempts to modify the returned multimap, either directly or through the + * multimap's views, result in an {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static Multimap unmodifiableMultimap( + Multimap delegate) { + if (delegate instanceof UnmodifiableMultimap || + delegate instanceof ImmutableMultimap) { + return delegate; + } + return new UnmodifiableMultimap(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static Multimap unmodifiableMultimap( + ImmutableMultimap delegate) { + return checkNotNull(delegate); + } + + private static class UnmodifiableMultimap + extends ForwardingMultimap implements Serializable { + final Multimap delegate; + transient Collection> entries; + transient Multiset keys; + transient Set keySet; + transient Collection values; + transient Map> map; + + UnmodifiableMultimap(final Multimap delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override protected Multimap delegate() { + return delegate; + } + + @Override public void clear() { + throw new UnsupportedOperationException(); + } + + @Override public Map> asMap() { + Map> result = map; + if (result == null) { + final Map> unmodifiableMap + = Collections.unmodifiableMap(delegate.asMap()); + map = result = new ForwardingMap>() { + @Override protected Map> delegate() { + return unmodifiableMap; + } + + Set>> entrySet; + + @Override public Set>> entrySet() { + Set>> result = entrySet; + return (result == null) + ? entrySet + = unmodifiableAsMapEntries(unmodifiableMap.entrySet()) + : result; + } + + @Override public Collection get(Object key) { + Collection collection = unmodifiableMap.get(key); + return (collection == null) + ? null : unmodifiableValueCollection(collection); + } + + Collection> asMapValues; + + @Override public Collection> values() { + Collection> result = asMapValues; + return (result == null) + ? asMapValues + = new UnmodifiableAsMapValues(unmodifiableMap.values()) + : result; + } + + @Override public boolean containsValue(Object o) { + return values().contains(o); + } + }; + } + return result; + } + + @Override public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = unmodifiableEntries(delegate.entries()); + } + return result; + } + + @Override public Collection get(K key) { + return unmodifiableValueCollection(delegate.get(key)); + } + + @Override public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = Multisets.unmodifiableMultiset(delegate.keys()); + } + return result; + } + + @Override public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = Collections.unmodifiableSet(delegate.keySet()); + } + return result; + } + + @Override public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override public Collection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override public Collection replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override public Collection values() { + Collection result = values; + if (result == null) { + values = result = Collections.unmodifiableCollection(delegate.values()); + } + return result; + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableAsMapValues + extends ForwardingCollection> { + final Collection> delegate; + UnmodifiableAsMapValues(Collection> delegate) { + this.delegate = Collections.unmodifiableCollection(delegate); + } + @Override protected Collection> delegate() { + return delegate; + } + @Override public Iterator> iterator() { + final Iterator> iterator = delegate.iterator(); + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public Collection next() { + return unmodifiableValueCollection(iterator.next()); + } + }; + } + @Override public Object[] toArray() { + return standardToArray(); + } + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + @Override public boolean contains(Object o) { + return standardContains(o); + } + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + } + + private static class UnmodifiableListMultimap + extends UnmodifiableMultimap implements ListMultimap { + UnmodifiableListMultimap(ListMultimap delegate) { + super(delegate); + } + @Override public ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + @Override public List get(K key) { + return Collections.unmodifiableList(delegate().get(key)); + } + @Override public List removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public List replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSetMultimap + extends UnmodifiableMultimap implements SetMultimap { + UnmodifiableSetMultimap(SetMultimap delegate) { + super(delegate); + } + @Override public SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + @Override public Set get(K key) { + /* + * Note that this doesn't return a SortedSet when delegate is a + * SortedSetMultiset, unlike (SortedSet) super.get(). + */ + return Collections.unmodifiableSet(delegate().get(key)); + } + @Override public Set> entries() { + return Maps.unmodifiableEntrySet(delegate().entries()); + } + @Override public Set removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public Set replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSortedSetMultimap + extends UnmodifiableSetMultimap implements SortedSetMultimap { + UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { + super(delegate); + } + @Override public SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + @Override public SortedSet get(K key) { + return Collections.unmodifiableSortedSet(delegate().get(key)); + } + @Override public SortedSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public SortedSet replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + @Override + public Comparator valueComparator() { + return delegate().valueComparator(); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) {@code SetMultimap} backed by the + * specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SetMultimap synchronizedSetMultimap( + SetMultimap multimap) { + return Synchronized.setMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SetMultimap}. Query + * operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static SetMultimap unmodifiableSetMultimap( + SetMultimap delegate) { + if (delegate instanceof UnmodifiableSetMultimap || + delegate instanceof ImmutableSetMultimap) { + return delegate; + } + return new UnmodifiableSetMultimap(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static SetMultimap unmodifiableSetMultimap( + ImmutableSetMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code SortedSetMultimap} backed by + * the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SortedSetMultimap + synchronizedSortedSetMultimap(SortedSetMultimap multimap) { + return Synchronized.sortedSetMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SortedSetMultimap}. + * Query operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static SortedSetMultimap unmodifiableSortedSetMultimap( + SortedSetMultimap delegate) { + if (delegate instanceof UnmodifiableSortedSetMultimap) { + return delegate; + } + return new UnmodifiableSortedSetMultimap(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code ListMultimap} backed by the + * specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static ListMultimap synchronizedListMultimap( + ListMultimap multimap) { + return Synchronized.listMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code ListMultimap}. Query + * operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static ListMultimap unmodifiableListMultimap( + ListMultimap delegate) { + if (delegate instanceof UnmodifiableListMultimap || + delegate instanceof ImmutableListMultimap) { + return delegate; + } + return new UnmodifiableListMultimap(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static ListMultimap unmodifiableListMultimap( + ImmutableListMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns an unmodifiable view of the specified collection, preserving the + * interface for instances of {@code SortedSet}, {@code Set}, {@code List} and + * {@code Collection}, in that order of preference. + * + * @param collection the collection for which to return an unmodifiable view + * @return an unmodifiable view of the collection + */ + private static Collection unmodifiableValueCollection( + Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } + return Collections.unmodifiableCollection(collection); + } + + /** + * Returns an unmodifiable view of the specified multimap {@code asMap} entry. + * The {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}, and the collection returned by {@code + * getValue} is also an unmodifiable (type-preserving) view. This also has the + * side-effect of redefining equals to comply with the Map.Entry contract, and + * to avoid a possible nefarious implementation of equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + private static Map.Entry> unmodifiableAsMapEntry( + final Map.Entry> entry) { + checkNotNull(entry); + return new AbstractMapEntry>() { + @Override public K getKey() { + return entry.getKey(); + } + + @Override public Collection getValue() { + return unmodifiableValueCollection(entry.getValue()); + } + }; + } + + /** + * Returns an unmodifiable view of the specified collection of entries. The + * {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}. If the specified collection is a {@code + * Set}, the returned collection is also a {@code Set}. + * + * @param entries the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + private static Collection> unmodifiableEntries( + Collection> entries) { + if (entries instanceof Set) { + return Maps.unmodifiableEntrySet((Set>) entries); + } + return new Maps.UnmodifiableEntries( + Collections.unmodifiableCollection(entries)); + } + + /** + * Returns an unmodifiable view of the specified set of {@code asMap} entries. + * The {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}, as do any operations that attempt to modify + * the returned collection. + * + * @param asMapEntries the {@code asMap} entries for which to return an + * unmodifiable view + * @return an unmodifiable view of the collection entries + */ + private static Set>> unmodifiableAsMapEntries( + Set>> asMapEntries) { + return new UnmodifiableAsMapEntries( + Collections.unmodifiableSet(asMapEntries)); + } + + /** @see Multimaps#unmodifiableAsMapEntries */ + static class UnmodifiableAsMapEntries + extends ForwardingSet>> { + private final Set>> delegate; + UnmodifiableAsMapEntries(Set>> delegate) { + this.delegate = delegate; + } + + @Override protected Set>> delegate() { + return delegate; + } + + @Override public Iterator>> iterator() { + final Iterator>> iterator = delegate.iterator(); + return new ForwardingIterator>>() { + @Override protected Iterator>> delegate() { + return iterator; + } + @Override public Entry> next() { + return unmodifiableAsMapEntry(iterator.next()); + } + }; + } + + @Override public Object[] toArray() { + return standardToArray(); + } + + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + + @Override public boolean equals(@Nullable Object object) { + return standardEquals(object); + } + } + + /** + * Returns a multimap view of the specified map. The multimap is backed by the + * map, so changes to the map are reflected in the multimap, and vice versa. + * If the map is modified while an iteration over one of the multimap's + * collection views is in progress (except through the iterator's own {@code + * remove} operation, or through the {@code setValue} operation on a map entry + * returned by the iterator), the results of the iteration are undefined. + * + *

The multimap supports mapping removal, which removes the corresponding + * mapping from the map. It does not support any operations which might add + * mappings, such as {@code put}, {@code putAll} or {@code replaceValues}. + * + *

The returned multimap will be serializable if the specified map is + * serializable. + * + * @param map the backing map for the returned multimap view + */ + public static SetMultimap forMap(Map map) { + return new MapMultimap(map); + } + + /** @see Multimaps#forMap */ + private static class MapMultimap + implements SetMultimap, Serializable { + final Map map; + transient Map> asMap; + + MapMultimap(Map map) { + this.map = checkNotNull(map); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public boolean containsEntry(Object key, Object value) { + return map.entrySet().contains(Maps.immutableEntry(key, value)); + } + + @Override + public Set get(final K key) { + return new Sets.ImprovedAbstractSet() { + @Override public Iterator iterator() { + return new Iterator() { + int i; + + @Override + public boolean hasNext() { + return (i == 0) && map.containsKey(key); + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + i++; + return map.get(key); + } + + @Override + public void remove() { + checkState(i == 1); + i = -1; + map.remove(key); + } + }; + } + + @Override public int size() { + return map.containsKey(key) ? 1 : 0; + } + }; + } + + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public Set replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + return map.entrySet().remove(Maps.immutableEntry(key, value)); + } + + @Override + public Set removeAll(Object key) { + Set values = new HashSet(2); + if (!map.containsKey(key)) { + return values; + } + values.add(map.remove(key)); + return values; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Multiset keys() { + return Multisets.forSet(map.keySet()); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entries() { + return map.entrySet(); + } + + @Override + public Map> asMap() { + Map> result = asMap; + if (result == null) { + asMap = result = new AsMap(); + } + return result; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.size() == that.size() && asMap().equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + private static final MapJoiner JOINER + = Joiner.on("], ").withKeyValueSeparator("=[").useForNull("null"); + + @Override public String toString() { + if (map.isEmpty()) { + return "{}"; + } + StringBuilder builder + = Collections2.newStringBuilderForCollection(map.size()).append('{'); + JOINER.appendTo(builder, map); + return builder.append("]}").toString(); + } + + /** @see MapMultimap#asMap */ + class AsMapEntries extends Sets.ImprovedAbstractSet>> { + @Override public int size() { + return map.size(); + } + + @Override public Iterator>> iterator() { + return new TransformedIterator>>(map.keySet().iterator()) { + @Override + Entry> transform(final K key) { + return new AbstractMapEntry>() { + @Override + public K getKey() { + return key; + } + + @Override + public Collection getValue() { + return get(key); + } + }; + } + }; + } + + @Override public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + if (!(entry.getValue() instanceof Set)) { + return false; + } + Set set = (Set) entry.getValue(); + return (set.size() == 1) + && containsEntry(entry.getKey(), set.iterator().next()); + } + + @Override public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + if (!(entry.getValue() instanceof Set)) { + return false; + } + Set set = (Set) entry.getValue(); + return (set.size() == 1) + && map.entrySet().remove( + Maps.immutableEntry(entry.getKey(), set.iterator().next())); + } + } + + /** @see MapMultimap#asMap */ + class AsMap extends Maps.ImprovedAbstractMap> { + @Override protected Set>> createEntrySet() { + return new AsMapEntries(); + } + + // The following methods are included for performance. + + @Override public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @SuppressWarnings("unchecked") + @Override public Collection get(Object key) { + Collection collection = MapMultimap.this.get((K) key); + return collection.isEmpty() ? null : collection; + } + + @Override public Collection remove(Object key) { + Collection collection = removeAll(key); + return collection.isEmpty() ? null : collection; + } + } + private static final long serialVersionUID = 7845222491160860175L; + } + + /** + * Returns a view of a multimap where each value is transformed by a function. + * All other properties of the multimap, such as iteration order, are left + * intact. For example, the code:

   {@code
+   *
+   * Multimap multimap =
+   *     ImmutableSetMultimap.of("a", 2, "b", -3, "b", -3, "a", 4, "c", 6);
+   * Function square = new Function() {
+   *     public String apply(Integer in) {
+   *       return Integer.toString(in * in);
+   *     }
+   * };
+   * Multimap transformed =
+   *     Multimaps.transformValues(multimap, square);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=[4, 16], b=[9, 9], c=[6]}}. + * + *

Changes in the underlying multimap are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and + * even null values provided that the function is capable of accepting null + * input. The transformed multimap might contain null values, if the function + * sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the + * underlying multimap is. The {@code equals} and {@code hashCode} methods + * of the returned multimap are meaningless, since there is not a definition + * of {@code equals} or {@code hashCode} for general collections, and + * {@code get()} will return a general {@code Collection} as opposed to a + * {@code List} or a {@code Set}. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned multimap to be a view, but it means that the function will + * be applied many times for bulk operations like + * {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation + * when the returned multimap doesn't need to be a view, copy the returned + * multimap into a new multimap of your choosing. + * + * @since 7.0 + */ + public static Multimap transformValues( + Multimap fromMultimap, final Function function) { + checkNotNull(function); + EntryTransformer transformer = + new EntryTransformer() { + @Override + public V2 transformEntry(K key, V1 value) { + return function.apply(value); + } + }; + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a multimap whose values are derived from the original + * multimap's entries. In contrast to {@link #transformValues}, this method's + * entry-transformation logic may depend on the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration + * order, are left intact. For example, the code:

   {@code
+   *
+   *   SetMultimap multimap =
+   *       ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
+   *   EntryTransformer transformer =
+   *       new EntryTransformer() {
+   *         public String transformEntry(String key, Integer value) {
+   *            return (value >= 0) ? key : "no" + key;
+   *         }
+   *       };
+   *   Multimap transformed =
+   *       Multimaps.transformEntries(multimap, transformer);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=[a, a], b=[nob]}}. + * + *

Changes in the underlying multimap are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and + * null values provided that the transformer is capable of accepting null + * inputs. The transformed multimap might contain null values if the + * transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the + * underlying multimap is. The {@code equals} and {@code hashCode} methods + * of the returned multimap are meaningless, since there is not a definition + * of {@code equals} or {@code hashCode} for general collections, and + * {@code get()} will return a general {@code Collection} as opposed to a + * {@code List} or a {@code Set}. + * + *

The transformer is applied lazily, invoked when needed. This is + * necessary for the returned multimap to be a view, but it means that the + * transformer will be applied many times for bulk operations like {@link + * Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the + * returned multimap doesn't need to be a view, copy the returned multimap + * into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of + * {@code EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies + * that {@code k2} is also of type {@code K}. Using an {@code + * EntryTransformer} key type for which this may not hold, such as {@code + * ArrayList}, may risk a {@code ClassCastException} when calling methods on + * the transformed multimap. + * + * @since 7.0 + */ + public static Multimap transformEntries( + Multimap fromMap, + EntryTransformer transformer) { + return new TransformedEntriesMultimap(fromMap, transformer); + } + + private static class TransformedEntriesMultimap + implements Multimap { + final Multimap fromMultimap; + final EntryTransformer transformer; + + TransformedEntriesMultimap(Multimap fromMultimap, + final EntryTransformer transformer) { + this.fromMultimap = checkNotNull(fromMultimap); + this.transformer = checkNotNull(transformer); + } + + Collection transform(final K key, Collection values) { + return Collections2.transform(values, new Function() { + @Override public V2 apply(V1 value) { + return transformer.transformEntry(key, value); + } + }); + } + + private transient Map> asMap; + + @Override public Map> asMap() { + if (asMap == null) { + Map> aM = Maps.transformEntries(fromMultimap.asMap(), + new EntryTransformer, Collection>() { + + @Override public Collection transformEntry( + K key, Collection value) { + return transform(key, value); + } + }); + asMap = aM; + return aM; + } + return asMap; + } + + @Override public void clear() { + fromMultimap.clear(); + } + + @SuppressWarnings("unchecked") + @Override public boolean containsEntry(Object key, Object value) { + Collection values = get((K) key); + return values.contains(value); + } + + @Override public boolean containsKey(Object key) { + return fromMultimap.containsKey(key); + } + + @Override public boolean containsValue(Object value) { + return values().contains(value); + } + + private transient Collection> entries; + + @Override public Collection> entries() { + if (entries == null) { + Collection> es = new TransformedEntries(transformer); + entries = es; + return es; + } + return entries; + } + + private class TransformedEntries + extends TransformedCollection, Entry> { + + TransformedEntries( + final EntryTransformer transformer) { + super(fromMultimap.entries(), + new Function, Entry>() { + @Override public Entry apply(final Entry entry) { + return new AbstractMapEntry() { + + @Override public K getKey() { + return entry.getKey(); + } + + @Override public V2 getValue() { + return transformer.transformEntry( + entry.getKey(), entry.getValue()); + } + }; + } + }); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + return containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @SuppressWarnings("unchecked") + @Override public boolean remove(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + Collection values = get((K) entry.getKey()); + return values.remove(entry.getValue()); + } + return false; + } + + } + + @Override public Collection get(final K key) { + return transform(key, fromMultimap.get(key)); + } + + @Override public boolean isEmpty() { + return fromMultimap.isEmpty(); + } + + @Override public Set keySet() { + return fromMultimap.keySet(); + } + + @Override public Multiset keys() { + return fromMultimap.keys(); + } + + @Override public boolean put(K key, V2 value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override public boolean putAll( + Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override public boolean remove(Object key, Object value) { + return get((K) key).remove(value); + } + + @SuppressWarnings("unchecked") + @Override public Collection removeAll(Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override public Collection replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override public int size() { + return fromMultimap.size(); + } + + private transient Collection values; + + @Override public Collection values() { + if (values == null) { + Collection vs = Collections2.transform( + fromMultimap.entries(), new Function, V2>() { + + @Override public V2 apply(Entry entry) { + return transformer.transformEntry( + entry.getKey(), entry.getValue()); + } + }); + values = vs; + return vs; + } + return values; + } + + @Override public boolean equals(Object obj) { + if (obj instanceof Multimap) { + Multimap other = (Multimap) obj; + return asMap().equals(other.asMap()); + } + return false; + } + + @Override public int hashCode() { + return asMap().hashCode(); + } + + @Override public String toString() { + return asMap().toString(); + } + } + + /** + * Returns a view of a {@code ListMultimap} where each value is transformed by + * a function. All other properties of the multimap, such as iteration order, + * are left intact. For example, the code:

   {@code
+   *
+   *   ListMultimap multimap
+   *        = ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
+   *   Function sqrt =
+   *       new Function() {
+   *         public Double apply(Integer in) {
+   *           return Math.sqrt((int) in);
+   *         }
+   *       };
+   *   ListMultimap transformed = Multimaps.transformValues(map,
+   *       sqrt);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=[2.0, 4.0], b=[3.0]}}. + * + *

Changes in the underlying multimap are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and + * even null values provided that the function is capable of accepting null + * input. The transformed multimap might contain null values, if the function + * sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the + * underlying multimap is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned multimap to be a view, but it means that the function will + * be applied many times for bulk operations like + * {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation + * when the returned multimap doesn't need to be a view, copy the returned + * multimap into a new multimap of your choosing. + * + * @since 7.0 + */ + public static ListMultimap transformValues( + ListMultimap fromMultimap, + final Function function) { + checkNotNull(function); + EntryTransformer transformer = + new EntryTransformer() { + @Override + public V2 transformEntry(K key, V1 value) { + return function.apply(value); + } + }; + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a {@code ListMultimap} whose values are derived from the + * original multimap's entries. In contrast to + * {@link #transformValues(ListMultimap, Function)}, this method's + * entry-transformation logic may depend on the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration + * order, are left intact. For example, the code:

   {@code
+   *
+   *   Multimap multimap =
+   *       ImmutableMultimap.of("a", 1, "a", 4, "b", 6);
+   *   EntryTransformer transformer =
+   *       new EntryTransformer() {
+   *         public String transformEntry(String key, Integer value) {
+   *           return key + value;
+   *         }
+   *       };
+   *   Multimap transformed =
+   *       Multimaps.transformEntries(multimap, transformer);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {"a"=["a1", "a4"], "b"=["b6"]}}. + * + *

Changes in the underlying multimap are reflected in this view. + * Conversely, this view supports removal operations, and these are reflected + * in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and + * null values provided that the transformer is capable of accepting null + * inputs. The transformed multimap might contain null values if the + * transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the + * underlying multimap is. + * + *

The transformer is applied lazily, invoked when needed. This is + * necessary for the returned multimap to be a view, but it means that the + * transformer will be applied many times for bulk operations like {@link + * Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the + * returned multimap doesn't need to be a view, copy the returned multimap + * into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of + * {@code EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies + * that {@code k2} is also of type {@code K}. Using an {@code + * EntryTransformer} key type for which this may not hold, such as {@code + * ArrayList}, may risk a {@code ClassCastException} when calling methods on + * the transformed multimap. + * + * @since 7.0 + */ + public static ListMultimap transformEntries( + ListMultimap fromMap, + EntryTransformer transformer) { + return new TransformedEntriesListMultimap(fromMap, transformer); + } + + private static final class TransformedEntriesListMultimap + extends TransformedEntriesMultimap + implements ListMultimap { + + TransformedEntriesListMultimap(ListMultimap fromMultimap, + EntryTransformer transformer) { + super(fromMultimap, transformer); + } + + @Override List transform(final K key, Collection values) { + return Lists.transform((List) values, new Function() { + @Override public V2 apply(V1 value) { + return transformer.transformEntry(key, value); + } + }); + } + + @Override public List get(K key) { + return transform(key, fromMultimap.get(key)); + } + + @SuppressWarnings("unchecked") + @Override public List removeAll(Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override public List replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of + * applying a specified function to each item in an {@code Iterable} of + * values. Each value will be stored as a value in the resulting multimap, + * yielding a multimap with the same size as the input iterable. The key used + * to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable + * snapshot. In the returned multimap, keys appear in the order they are first + * encountered, and the values corresponding to each key appear in the same + * order as they are encountered. + * + *

For example,

   {@code
+   *
+   *   List badGuys =
+   *       Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   *   Function stringLengthFunction = ...;
+   *   Multimap index =
+   *       Multimaps.index(badGuys, stringLengthFunction);
+   *   System.out.println(index);}
+ * + * prints
   {@code
+   *
+   *   {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}}
+ * + * The returned multimap is serializable if its keys and values are all + * serializable. + * + * @param values the values to use when constructing the {@code + * ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the + * function {@code keyFunction} on each value in the input collection to + * that value + * @throws NullPointerException if any of the following cases is true: + *
    + *
  • {@code values} is null + *
  • {@code keyFunction} is null + *
  • An element in {@code values} is null + *
  • {@code keyFunction} returns {@code null} for any element of {@code + * values} + *
+ */ + public static ImmutableListMultimap index( + Iterable values, Function keyFunction) { + return index(values.iterator(), keyFunction); + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of + * applying a specified function to each item in an {@code Iterator} of + * values. Each value will be stored as a value in the resulting multimap, + * yielding a multimap with the same size as the input iterator. The key used + * to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable + * snapshot. In the returned multimap, keys appear in the order they are first + * encountered, and the values corresponding to each key appear in the same + * order as they are encountered. + * + *

For example,

   {@code
+   *
+   *   List badGuys =
+   *       Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   *   Function stringLengthFunction = ...;
+   *   Multimap index =
+   *       Multimaps.index(badGuys.iterator(), stringLengthFunction);
+   *   System.out.println(index);}
+ * + * prints
   {@code
+   *
+   *   {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}}
+ * + * The returned multimap is serializable if its keys and values are all + * serializable. + * + * @param values the values to use when constructing the {@code + * ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the + * function {@code keyFunction} on each value in the input collection to + * that value + * @throws NullPointerException if any of the following cases is true: + *
    + *
  • {@code values} is null + *
  • {@code keyFunction} is null + *
  • An element in {@code values} is null + *
  • {@code keyFunction} returns {@code null} for any element of {@code + * values} + *
+ * @since 10.0 + */ + public static ImmutableListMultimap index( + Iterator values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + while (values.hasNext()) { + V value = values.next(); + checkNotNull(value, values); + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + static abstract class Keys extends AbstractMultiset { + abstract Multimap multimap(); + + @Override Iterator> entryIterator() { + return new TransformedIterator>, Multiset.Entry>( + multimap().asMap().entrySet().iterator()) { + @Override + Multiset.Entry transform( + final Map.Entry> backingEntry) { + return new Multisets.AbstractEntry() { + @Override + public K getElement() { + return backingEntry.getKey(); + } + + @Override + public int getCount() { + return backingEntry.getValue().size(); + } + }; + } + }; + } + + @Override int distinctElements() { + return multimap().asMap().size(); + } + + @Override Set> createEntrySet() { + return new KeysEntrySet(); + } + + class KeysEntrySet extends Multisets.EntrySet { + @Override Multiset multiset() { + return Keys.this; + } + + @Override public Iterator> iterator() { + return entryIterator(); + } + + @Override public int size() { + return distinctElements(); + } + + @Override public boolean isEmpty() { + return multimap().isEmpty(); + } + + @Override public boolean contains(@Nullable Object o) { + if (o instanceof Multiset.Entry) { + Multiset.Entry entry = (Multiset.Entry) o; + Collection collection = multimap().asMap().get(entry.getElement()); + return collection != null && collection.size() == entry.getCount(); + } + return false; + } + + @Override public boolean remove(@Nullable Object o) { + if (o instanceof Multiset.Entry) { + Multiset.Entry entry = (Multiset.Entry) o; + Collection collection = multimap().asMap().get(entry.getElement()); + if (collection != null && collection.size() == entry.getCount()) { + collection.clear(); + return true; + } + } + return false; + } + } + + @Override public boolean contains(@Nullable Object element) { + return multimap().containsKey(element); + } + + @Override public Iterator iterator() { + return Maps.keyIterator(multimap().entries().iterator()); + } + + @Override public int count(@Nullable Object element) { + try { + if (multimap().containsKey(element)) { + Collection values = multimap().asMap().get(element); + return (values == null) ? 0 : values.size(); + } + return 0; + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } + } + + @Override public int remove(@Nullable Object element, int occurrences) { + checkArgument(occurrences >= 0); + if (occurrences == 0) { + return count(element); + } + + Collection values; + try { + values = multimap().asMap().get(element); + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } + + if (values == null) { + return 0; + } + + int oldCount = values.size(); + if (occurrences >= oldCount) { + values.clear(); + } else { + Iterator iterator = values.iterator(); + for (int i = 0; i < occurrences; i++) { + iterator.next(); + iterator.remove(); + } + } + return oldCount; + } + + @Override public void clear() { + multimap().clear(); + } + + @Override public Set elementSet() { + return multimap().keySet(); + } + } + + static abstract class Values extends AbstractCollection { + abstract Multimap multimap(); + + @Override public Iterator iterator() { + return Maps.valueIterator(multimap().entries().iterator()); + } + + @Override public int size() { + return multimap().size(); + } + + @Override public boolean contains(@Nullable Object o) { + return multimap().containsValue(o); + } + + @Override public void clear() { + multimap().clear(); + } + } + + /** + * A skeleton implementation of {@link Multimap#entries()}. + */ + static abstract class Entries extends + AbstractCollection> { + abstract Multimap multimap(); + + @Override public int size() { + return multimap().size(); + } + + @Override public boolean contains(@Nullable Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override public boolean remove(@Nullable Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override public void clear() { + multimap().clear(); + } + } + + /** + * A skeleton implementation of {@link SetMultimap#entries()}. + */ + static abstract class EntrySet extends Entries implements + Set> { + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override public boolean equals(@Nullable Object obj) { + return Sets.equalsImpl(this, obj); + } + } + + /** + * A skeleton implementation of {@link Multimap#asMap()}. + */ + static abstract class AsMap extends + Maps.ImprovedAbstractMap> { + abstract Multimap multimap(); + + @Override public abstract int size(); + + abstract Iterator>> entryIterator(); + + @Override protected Set>> createEntrySet() { + return new EntrySet(); + } + + void removeValuesForKey(Object key){ + multimap().removeAll(key); + } + + class EntrySet extends Maps.EntrySet> { + @Override Map> map() { + return AsMap.this; + } + + @Override public Iterator>> iterator() { + return entryIterator(); + } + + @Override public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + @SuppressWarnings("unchecked") + @Override public Collection get(Object key) { + return containsKey(key) ? multimap().get((K) key) : null; + } + + @Override public Collection remove(Object key) { + return containsKey(key) ? multimap().removeAll(key) : null; + } + + @Override public Set keySet() { + return multimap().keySet(); + } + + @Override public boolean isEmpty() { + return multimap().isEmpty(); + } + + @Override public boolean containsKey(Object key) { + return multimap().containsKey(key); + } + + @Override public void clear() { + multimap().clear(); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys + * satisfy a predicate. The returned multimap is a live view of + * {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support + * {@code remove()}, but all other methods are supported by the multimap and + * its views. When adding a key that doesn't satisfy the predicate, the + * multimap's {@code put()}, {@code putAll()}, and {@code replaceValues()} + * methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on + * the filtered multimap or its views, only mappings whose keys satisfy the + * filter will be removed from the underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate + * across every key/value mapping in the underlying multimap and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered multimap and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, + * as documented at {@link Predicate#apply}. Do not provide a predicate such + * as {@code Predicates.instanceOf(ArrayList.class)}, which is inconsistent + * with equals. + * + * @since 11.0 + */ + @GwtIncompatible(value = "untested") + public static Multimap filterKeys( + Multimap unfiltered, final Predicate keyPredicate) { + checkNotNull(keyPredicate); + Predicate> entryPredicate = + new Predicate>() { + @Override + public boolean apply(Entry input) { + return keyPredicate.apply(input.getKey()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose values + * satisfy a predicate. The returned multimap is a live view of + * {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support + * {@code remove()}, but all other methods are supported by the multimap and + * its views. When adding a value that doesn't satisfy the predicate, the + * multimap's {@code put()}, {@code putAll()}, and {@code replaceValues()} + * methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on + * the filtered multimap or its views, only mappings whose value satisfy the + * filter will be removed from the underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate + * across every key/value mapping in the underlying multimap and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered multimap and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. Do not provide a + * predicate such as {@code Predicates.instanceOf(ArrayList.class)}, which is + * inconsistent with equals. + * + * @since 11.0 + */ + @GwtIncompatible(value = "untested") + public static Multimap filterValues( + Multimap unfiltered, final Predicate valuePredicate) { + checkNotNull(valuePredicate); + Predicate> entryPredicate = + new Predicate>() { + @Override + public boolean apply(Entry input) { + return valuePredicate.apply(input.getValue()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} that + * satisfy a predicate. The returned multimap is a live view of + * {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support + * {@code remove()}, but all other methods are supported by the multimap and + * its views. When adding a key/value pair that doesn't satisfy the predicate, + * multimap's {@code put()}, {@code putAll()}, and {@code replaceValues()} + * methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on + * the filtered multimap or its views, only mappings whose keys satisfy the + * filter will be removed from the underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate + * across every key/value mapping in the underlying multimap and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered multimap and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with + * equals, as documented at {@link Predicate#apply}. + * + * @since 11.0 + */ + @GwtIncompatible(value = "untested") + public static Multimap filterEntries( + Multimap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredMultimap) + ? filterFiltered((FilteredMultimap) unfiltered, entryPredicate) + : new FilteredMultimap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Support removal operations when filtering a filtered multimap. Since a + * filtered multimap has iterators that don't support remove, passing one to + * the FilteredMultimap constructor would lead to a multimap whose removal + * operations would fail. This method combines the predicates to avoid that + * problem. + */ + private static Multimap filterFiltered(FilteredMultimap map, + Predicate> entryPredicate) { + Predicate> predicate + = Predicates.and(map.predicate, entryPredicate); + return new FilteredMultimap(map.unfiltered, predicate); + } + + private static class FilteredMultimap implements Multimap { + final Multimap unfiltered; + final Predicate> predicate; + + FilteredMultimap(Multimap unfiltered, Predicate> predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + @Override public int size() { + return entries().size(); + } + + @Override public boolean isEmpty() { + return entries().isEmpty(); + } + + @Override public boolean containsKey(Object key) { + return asMap().containsKey(key); + } + + @Override public boolean containsValue(Object value) { + return values().contains(value); + } + + // This method should be called only when key is a K and value is a V. + @SuppressWarnings("unchecked") + boolean satisfiesPredicate(Object key, Object value) { + return predicate.apply(Maps.immutableEntry((K) key, (V) value)); + } + + @Override public boolean containsEntry(Object key, Object value) { + return unfiltered.containsEntry(key, value) && satisfiesPredicate(key, value); + } + + @Override public boolean put(K key, V value) { + checkArgument(satisfiesPredicate(key, value)); + return unfiltered.put(key, value); + } + + @Override public boolean remove(Object key, Object value) { + return containsEntry(key, value) ? unfiltered.remove(key, value) : false; + } + + @Override public boolean putAll(K key, Iterable values) { + for (V value : values) { + checkArgument(satisfiesPredicate(key, value)); + } + return unfiltered.putAll(key, values); + } + + @Override public boolean putAll(Multimap multimap) { + for (Entry entry : multimap.entries()) { + checkArgument(satisfiesPredicate(entry.getKey(), entry.getValue())); + } + return unfiltered.putAll(multimap); + } + + @Override public Collection replaceValues(K key, Iterable values) { + for (V value : values) { + checkArgument(satisfiesPredicate(key, value)); + } + // Not calling unfiltered.replaceValues() since values that don't satisify + // the filter should remain in the multimap. + Collection oldValues = removeAll(key); + unfiltered.putAll(key, values); + return oldValues; + } + + @Override public Collection removeAll(Object key) { + List removed = Lists.newArrayList(); + Collection values = unfiltered.asMap().get(key); + if (values != null) { + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + V value = iterator.next(); + if (satisfiesPredicate(key, value)) { + removed.add(value); + iterator.remove(); + } + } + } + if (unfiltered instanceof SetMultimap) { + return Collections.unmodifiableSet(Sets.newLinkedHashSet(removed)); + } else { + return Collections.unmodifiableList(removed); + } + } + + @Override public void clear() { + entries().clear(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return asMap().equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return asMap().hashCode(); + } + + @Override public String toString() { + return asMap().toString(); + } + + class ValuePredicate implements Predicate { + final K key; + ValuePredicate(K key) { + this.key = key; + } + @Override public boolean apply(V value) { + return satisfiesPredicate(key, value); + } + } + + Collection filterCollection(Collection collection, Predicate predicate) { + if (collection instanceof Set) { + return Sets.filter((Set) collection, predicate); + } else { + return Collections2.filter(collection, predicate); + } + } + + @Override public Collection get(K key) { + return filterCollection(unfiltered.get(key), new ValuePredicate(key)); + } + + @Override public Set keySet() { + return asMap().keySet(); + } + + Collection values; + + @Override public Collection values() { + return (values == null) ? values = new Values() : values; + } + + class Values extends Multimaps.Values { + @Override Multimap multimap() { + return FilteredMultimap.this; + } + + @Override public boolean contains(@Nullable Object o) { + return Iterators.contains(iterator(), o); + } + + // Override remove methods since iterator doesn't support remove. + + @Override public boolean remove(Object o) { + Iterator> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (Objects.equal(o, entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection c) { + boolean changed = false; + Iterator> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (c.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + boolean changed = false; + Iterator> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (!c.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + } + + Collection> entries; + + @Override public Collection> entries() { + return (entries == null) + ? entries = Collections2.filter(unfiltered.entries(), predicate) + : entries; + } + + /** + * Remove all filtered asMap() entries that satisfy the predicate. + */ + boolean removeEntriesIf(Predicate>> removalPredicate) { + Iterator>> iterator = unfiltered.asMap().entrySet().iterator(); + boolean changed = false; + while (iterator.hasNext()) { + // Determine whether to remove the filtered values with this key. + Map.Entry> entry = iterator.next(); + K key = entry.getKey(); + Collection collection = entry.getValue(); + Predicate valuePredicate = new ValuePredicate(key); + Collection filteredCollection = filterCollection(collection, valuePredicate); + Map.Entry> filteredEntry = Maps.immutableEntry(key, filteredCollection); + if (removalPredicate.apply(filteredEntry) && !filteredCollection.isEmpty()) { + changed = true; + if (Iterables.all(collection, valuePredicate)) { + iterator.remove(); // Remove all values for the key. + } else { + filteredCollection.clear(); // Remove the filtered values only. + } + } + } + return changed; + } + + Map> asMap; + + @Override public Map> asMap() { + return (asMap == null) ? asMap = createAsMap() : asMap; + } + + static final Predicate> NOT_EMPTY = new Predicate>() { + @Override public boolean apply(Collection input) { + return !input.isEmpty(); + } + }; + + Map> createAsMap() { + // Select the values that satisify the predicate. + EntryTransformer, Collection> transformer + = new EntryTransformer, Collection>() { + @Override public Collection transformEntry(K key, Collection collection) { + return filterCollection(collection, new ValuePredicate(key)); + } + }; + Map> transformed + = Maps.transformEntries(unfiltered.asMap(), transformer); + + // Select the keys that have at least one value remaining. + Map> filtered = Maps.filterValues(transformed, NOT_EMPTY); + + // Override the removal methods, since removing a map entry should not + // affect values that don't satisfy the filter. + return new AsMap(filtered); + } + + class AsMap extends ForwardingMap> { + final Map> delegate; + + AsMap(Map> delegate) { + this.delegate = delegate; + } + + @Override protected Map> delegate() { + return delegate; + } + + @Override public Collection remove(Object o) { + Collection output = FilteredMultimap.this.removeAll(o); + return output.isEmpty() ? null : output; + } + + @Override public void clear() { + FilteredMultimap.this.clear(); + } + + Set keySet; + + @Override public Set keySet() { + return (keySet == null) ? keySet = new KeySet() : keySet; + } + + class KeySet extends Maps.KeySet> { + @Override Map> map() { + return AsMap.this; + } + + @Override public boolean remove(Object o) { + Collection collection = delegate.get(o); + if (collection == null) { + return false; + } + collection.clear(); + return true; + } + + @Override public boolean removeAll(Collection c) { + return Sets.removeAllImpl(this, c.iterator()); + } + + @Override public boolean retainAll(final Collection c) { + Predicate>> removalPredicate + = new Predicate>>() { + @Override public boolean apply(Map.Entry> entry) { + return !c.contains(entry.getKey()); + } + }; + return removeEntriesIf(removalPredicate); + } + } + + Values asMapValues; + + @Override public Collection> values() { + return (asMapValues == null) ? asMapValues = new Values() : asMapValues; + } + + class Values extends Maps.Values> { + @Override Map> map() { + return AsMap.this; + } + + @Override public boolean remove(Object o) { + for (Collection collection : this) { + if (collection.equals(o)) { + collection.clear(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(final Collection c) { + Predicate>> removalPredicate + = new Predicate>>() { + @Override public boolean apply(Map.Entry> entry) { + return c.contains(entry.getValue()); + } + }; + return removeEntriesIf(removalPredicate); + } + + @Override public boolean retainAll(final Collection c) { + Predicate>> removalPredicate + = new Predicate>>() { + @Override public boolean apply(Map.Entry> entry) { + return !c.contains(entry.getValue()); + } + }; + return removeEntriesIf(removalPredicate); + } + } + + EntrySet entrySet; + + @Override public Set>> entrySet() { + return (entrySet == null) ? entrySet = new EntrySet(super.entrySet()) : entrySet; + } + + class EntrySet extends Maps.EntrySet> { + Set>> delegateEntries; + + public EntrySet(Set>> delegateEntries) { + this.delegateEntries = delegateEntries; + } + + @Override Map> map() { + return AsMap.this; + } + + @Override public Iterator>> iterator() { + return delegateEntries.iterator(); + } + + @Override public boolean remove(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + Collection collection = delegate.get(entry.getKey()); + if (collection != null && collection.equals(entry.getValue())) { + collection.clear(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection c) { + return Sets.removeAllImpl(this, c); + } + + @Override public boolean retainAll(final Collection c) { + Predicate>> removalPredicate + = new Predicate>>() { + @Override public boolean apply(Map.Entry> entry) { + return !c.contains(entry); + } + }; + return removeEntriesIf(removalPredicate); + } + } + } + + AbstractMultiset keys; + + @Override public Multiset keys() { + return (keys == null) ? keys = new Keys() : keys; + } + + class Keys extends Multimaps.Keys { + @Override Multimap multimap() { + return FilteredMultimap.this; + } + + @Override public int remove(Object o, int occurrences) { + checkArgument(occurrences >= 0); + Collection values = unfiltered.asMap().get(o); + if (values == null) { + return 0; + } + int priorCount = 0; + int removed = 0; + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + if (satisfiesPredicate(o, iterator.next())) { + priorCount++; + if (removed < occurrences) { + iterator.remove(); + removed++; + } + } + } + return priorCount; + } + + @Override Set> createEntrySet() { + return new EntrySet(); + } + + class EntrySet extends Multimaps.Keys.KeysEntrySet { + @Override + public boolean removeAll(final Collection c) { + return Sets.removeAllImpl(this, c.iterator()); + } + + @Override public boolean retainAll(final Collection c) { + Predicate>> removalPredicate + = new Predicate>>() { + @Override public boolean apply(Map.Entry> entry) { + Multiset.Entry multisetEntry + = Multisets.immutableEntry(entry.getKey(), entry.getValue().size()); + return !c.contains(multisetEntry); + } + }; + return removeEntriesIf(removalPredicate); + } + } + } + } + + // TODO(jlevy): Create methods that filter a SetMultimap or SortedSetMultimap. +} diff --git a/guava/src/com/google/common/collect/Multiset.java b/guava/src/com/google/common/collect/Multiset.java new file mode 100644 index 0000000..bb254c9 --- /dev/null +++ b/guava/src/com/google/common/collect/Multiset.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A collection that supports order-independent equality, like {@link Set}, but + * may have duplicate elements. A multiset is also sometimes called a + * bag. + * + *

Elements of a multiset that are equal to one another are referred to as + * occurrences of the same single element. The total number of + * occurrences of an element in a multiset is called the count of that + * element (the terms "frequency" and "multiplicity" are equivalent, but not + * used in this API). Since the count of an element is represented as an {@code + * int}, a multiset may never contain more than {@link Integer#MAX_VALUE} + * occurrences of any one element. + * + *

{@code Multiset} refines the specifications of several methods from + * {@code Collection}. It also defines an additional query operation, {@link + * #count}, which returns the count of an element. There are five new + * bulk-modification operations, for example {@link #add(Object, int)}, to add + * or remove multiple occurrences of an element at once, or to set the count of + * an element to a specific value. These modification operations are optional, + * but implementations which support the standard collection operations {@link + * #add(Object)} or {@link #remove(Object)} are encouraged to implement the + * related methods as well. Finally, two collection views are provided: {@link + * #elementSet} contains the distinct elements of the multiset "with duplicates + * collapsed", and {@link #entrySet} is similar but contains {@link Entry + * Multiset.Entry} instances, each providing both a distinct element and the + * count of that element. + * + *

In addition to these required methods, implementations of {@code + * Multiset} are expected to provide two {@code static} creation methods: + * {@code create()}, returning an empty multiset, and {@code + * create(Iterable)}, returning a multiset containing the + * given initial elements. This is simply a refinement of {@code Collection}'s + * constructor recommendations, reflecting the new developments of Java 5. + * + *

As with other collection types, the modification operations are optional, + * and should throw {@link UnsupportedOperationException} when they are not + * implemented. Most implementations should support either all add operations + * or none of them, all removal operations or none of them, and if and only if + * all of these are supported, the {@code setCount} methods as well. + * + *

A multiset uses {@link Object#equals} to determine whether two instances + * should be considered "the same," unless specified otherwise by the + * implementation. + * + *

Common implementations include {@link ImmutableMultiset}, {@link + * HashMultiset}, and {@link ConcurrentHashMultiset}. + * + *

If your values may be zero, negative, or outside the range of an int, you + * may wish to use {@link com.google.common.util.concurrent.AtomicLongMap} + * instead. Note, however, that unlike {@code Multiset}, {@code AtomicLongMap} + * does not automatically remove zeros. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface Multiset extends Collection { + // Query Operations + + /** + * Returns the number of occurrences of an element in this multiset (the + * count of the element). Note that for an {@link Object#equals}-based + * multiset, this gives the same result as {@link Collections#frequency} + * (which would presumably perform more poorly). + * + *

Note: the utility method {@link Iterables#frequency} generalizes + * this operation; it correctly delegates to this method when dealing with a + * multiset, but it can also accept any other iterable type. + * + * @param element the element to count occurrences of + * @return the number of occurrences of the element in this multiset; possibly + * zero but never negative + */ + int count(@Nullable Object element); + + // Bulk Operations + + /** + * Adds a number of occurrences of an element to this multiset. Note that if + * {@code occurrences == 1}, this method has the identical effect to {@link + * #add(Object)}. This method is functionally equivalent (except in the case + * of overflow) to the call {@code addAll(Collections.nCopies(element, + * occurrences))}, which would presumably perform much more poorly. + * + * @param element the element to add occurrences of; may be null only if + * explicitly allowed by the implementation + * @param occurrences the number of occurrences of the element to add. May be + * zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * this operation would result in more than {@link Integer#MAX_VALUE} + * occurrences of the element + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * occurrences} is zero, the implementation may opt to return normally. + */ + int add(@Nullable E element, int occurrences); + + /** + * Removes a number of occurrences of the specified element from this + * multiset. If the multiset contains fewer than this number of occurrences to + * begin with, all occurrences will be removed. Note that if + * {@code occurrences == 1}, this is functionally equivalent to the call + * {@code remove(element)}. + * + * @param element the element to conditionally remove occurrences of + * @param occurrences the number of occurrences of the element to remove. May + * be zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + int remove(@Nullable Object element, int occurrences); + + /** + * Adds or removes the necessary occurrences of an element such that the + * element attains the desired count. + * + * @param element the element to add or remove occurrences of; may be null + * only if explicitly allowed by the implementation + * @param count the desired count of the element in this multiset + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code count} is negative + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * count} is zero, the implementor may optionally return zero instead. + */ + int setCount(E element, int count); + + /** + * Conditionally sets the count of an element to a new value, as described in + * {@link #setCount(Object, int)}, provided that the element has the expected + * current count. If the current count is not {@code oldCount}, no change is + * made. + * + * @param element the element to conditionally set the count of; may be null + * only if explicitly allowed by the implementation + * @param oldCount the expected present count of the element in this multiset + * @param newCount the desired count of the element in this multiset + * @return {@code true} if the condition for modification was met. This + * implies that the multiset was indeed modified, unless + * {@code oldCount == newCount}. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is + * negative + * @throws NullPointerException if {@code element} is null and the + * implementation does not permit null elements. Note that if {@code + * oldCount} and {@code newCount} are both zero, the implementor may + * optionally return {@code true} instead. + */ + boolean setCount(E element, int oldCount, int newCount); + + // Views + + /** + * Returns the set of distinct elements contained in this multiset. The + * element set is backed by the same data as the multiset, so any change to + * either is immediately reflected in the other. The order of the elements in + * the element set is unspecified. + * + *

If the element set supports any removal operations, these necessarily + * cause all occurrences of the removed element(s) to be removed from + * the multiset. Implementations are not expected to support the add + * operations, although this is possible. + * + *

A common use for the element set is to find the number of distinct + * elements in the multiset: {@code elementSet().size()}. + * + * @return a view of the set of distinct elements in this multiset + */ + Set elementSet(); + + /** + * Returns a view of the contents of this multiset, grouped into {@code + * Multiset.Entry} instances, each providing an element of the multiset and + * the count of that element. This set contains exactly one entry for each + * distinct element in the multiset (thus it always has the same size as the + * {@link #elementSet}). The order of the elements in the element set is + * unspecified. + * + *

The entry set is backed by the same data as the multiset, so any change + * to either is immediately reflected in the other. However, multiset changes + * may or may not be reflected in any {@code Entry} instances already + * retrieved from the entry set (this is implementation-dependent). + * Furthermore, implementations are not required to support modifications to + * the entry set at all, and the {@code Entry} instances themselves don't + * even have methods for modification. See the specific implementation class + * for more details on how its entry set handles modifications. + * + * @return a set of entries representing the data of this multiset + */ + Set> entrySet(); + + /** + * An unmodifiable element-count pair for a multiset. The {@link + * Multiset#entrySet} method returns a view of the multiset whose elements + * are of this class. A multiset implementation may return Entry instances + * that are either live "read-through" views to the Multiset, or immutable + * snapshots. Note that this type is unrelated to the similarly-named type + * {@code Map.Entry}. + * + * @since 2.0 (imported from Google Collections Library) + */ + interface Entry { + + /** + * Returns the multiset element corresponding to this entry. Multiple calls + * to this method always return the same instance. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the count of the associated element in the underlying multiset. + * This count may either be an unchanging snapshot of the count at the time + * the entry was retrieved, or a live view of the current count of the + * element in the multiset, depending on the implementation. Note that in + * the former case, this method can never return zero, while in the latter, + * it will return zero if all occurrences of the element were since removed + * from the multiset. + * + * @return the count of the element; never negative + */ + int getCount(); + + /** + * {@inheritDoc} + * + *

Returns {@code true} if the given object is also a multiset entry and + * the two entries represent the same element and count. That is, two + * entries {@code a} and {@code b} are equal if:

   {@code
+     *
+     *   Objects.equal(a.getElement(), b.getElement())
+     *       && a.getCount() == b.getCount()}
+ */ + @Override + // TODO(kevinb): check this wrt TreeMultiset? + boolean equals(Object o); + + /** + * {@inheritDoc} + * + *

The hash code of a multiset entry for element {@code element} and + * count {@code count} is defined as:

   {@code
+     *
+     *   ((element == null) ? 0 : element.hashCode()) ^ count}
+ */ + @Override + int hashCode(); + + /** + * Returns the canonical string representation of this entry, defined as + * follows. If the count for this entry is one, this is simply the string + * representation of the corresponding element. Otherwise, it is the string + * representation of the element, followed by the three characters {@code + * " x "} (space, letter x, space), followed by the count. + */ + @Override + String toString(); + } + + // Comparison and hashing + + /** + * Compares the specified object with this multiset for equality. Returns + * {@code true} if the given object is also a multiset and contains equal + * elements with equal counts, regardless of order. + */ + @Override + // TODO(kevinb): caveats about equivalence-relation? + boolean equals(@Nullable Object object); + + /** + * Returns the hash code for this multiset. This is defined as the sum of + *
   {@code
+   *
+   *   ((element == null) ? 0 : element.hashCode()) ^ count(element)}
+ * + * over all distinct elements in the multiset. It follows that a multiset and + * its entry set always have the same hash code. + */ + @Override + int hashCode(); + + /** + * {@inheritDoc} + * + *

It is recommended, though not mandatory, that this method return the + * result of invoking {@link #toString} on the {@link #entrySet}, yielding a + * result such as {@code [a x 3, c, d x 2, e]}. + */ + @Override + String toString(); + + // Refined Collection Methods + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will appear + * multiple times in this iterator, though not necessarily sequentially. + */ + @Override + Iterator iterator(); + + /** + * Determines whether this multiset contains the specified element. + * + *

This method refines {@link Collection#contains} to further specify that + * it may not throw an exception in response to {@code element} being + * null or of the wrong type. + * + * @param element the element to check for + * @return {@code true} if this multiset contains at least one occurrence of + * the element + */ + @Override + boolean contains(@Nullable Object element); + + /** + * Returns {@code true} if this multiset contains at least one occurrence of + * each element in the specified collection. + * + *

This method refines {@link Collection#containsAll} to further specify + * that it may not throw an exception in response to any of {@code + * elements} being null or of the wrong type. + * + *

Note: this method does not take into account the occurrence + * count of an element in the two collections; it may still return {@code + * true} even if {@code elements} contains several occurrences of an element + * and this multiset contains only one. This is no different than any other + * collection type like {@link List}, but it may be unexpected to the user of + * a multiset. + * + * @param elements the collection of elements to be checked for containment in + * this multiset + * @return {@code true} if this multiset contains at least one occurrence of + * each element contained in {@code elements} + * @throws NullPointerException if {@code elements} is null + */ + @Override + boolean containsAll(Collection elements); + + /** + * Adds a single occurrence of the specified element to this multiset. + * + *

This method refines {@link Collection#add}, which only ensures + * the presence of the element, to further specify that a successful call must + * always increment the count of the element, and the overall size of the + * collection, by one. + * + * @param element the element to add one occurrence of; may be null only if + * explicitly allowed by the implementation + * @return {@code true} always, since this call is required to modify the + * multiset, unlike other {@link Collection} types + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements + * @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences + * of {@code element} are already contained in this multiset + */ + @Override + boolean add(E element); + + /** + * Removes a single occurrence of the specified element from this + * multiset, if present. + * + *

This method refines {@link Collection#remove} to further specify that it + * may not throw an exception in response to {@code element} being null + * or of the wrong type. + * + * @param element the element to remove one occurrence of + * @return {@code true} if an occurrence was found and removed + */ + @Override + boolean remove(@Nullable Object element); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in + * {@code c}, and only cares whether or not an element appears at all. + * If you wish to remove one occurrence in this multiset for every occurrence + * in {@code c}, see {@link Multisets#removeOccurrences(Multiset, Multiset)}. + * + *

This method refines {@link Collection#removeAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + */ + @Override + boolean removeAll(Collection c); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in + * {@code c}, and only cares whether or not an element appears at all. + * If you wish to remove one occurrence in this multiset for every occurrence + * in {@code c}, see {@link Multisets#retainOccurrences(Multiset, Multiset)}. + * + *

This method refines {@link Collection#retainAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + * + * @see Multisets#retainOccurrences(Multiset, Multiset) + */ + @Override + boolean retainAll(Collection c); +} diff --git a/guava/src/com/google/common/collect/Multisets.java b/guava/src/com/google/common/collect/Multisets.java new file mode 100644 index 0000000..60e1d26 --- /dev/null +++ b/guava/src/com/google/common/collect/Multisets.java @@ -0,0 +1,972 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import com.google.common.collect.Multiset.Entry; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Provides static utility methods for creating and working with {@link + * Multiset} instances. + * + *

See the Guava User Guide article on + * {@code Multisets}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public final class Multisets { + private Multisets() {} + + /** + * Returns an unmodifiable view of the specified multiset. Query operations on + * the returned multiset "read through" to the specified multiset, and + * attempts to modify the returned multiset result in an + * {@link UnsupportedOperationException}. + * + *

The returned multiset will be serializable if the specified multiset is + * serializable. + * + * @param multiset the multiset for which an unmodifiable view is to be + * generated + * @return an unmodifiable view of the multiset + */ + public static Multiset unmodifiableMultiset( + Multiset multiset) { + if (multiset instanceof UnmodifiableMultiset || + multiset instanceof ImmutableMultiset) { + // Since it's unmodifiable, the covariant cast is safe + @SuppressWarnings("unchecked") + Multiset result = (Multiset) multiset; + return result; + } + return new UnmodifiableMultiset(checkNotNull(multiset)); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated public static Multiset unmodifiableMultiset( + ImmutableMultiset multiset) { + return checkNotNull(multiset); + } + + static class UnmodifiableMultiset + extends ForwardingMultiset implements Serializable { + final Multiset delegate; + + UnmodifiableMultiset(Multiset delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override protected Multiset delegate() { + // This is safe because all non-covariant methods are overriden + return (Multiset) delegate; + } + + transient Set elementSet; + + Set createElementSet() { + return Collections.unmodifiableSet(delegate.elementSet()); + } + + @Override + public Set elementSet() { + Set es = elementSet; + return (es == null) ? elementSet = createElementSet() : es; + } + + transient Set> entrySet; + + @SuppressWarnings("unchecked") + @Override public Set> entrySet() { + Set> es = entrySet; + return (es == null) + // Safe because the returned set is made unmodifiable and Entry + // itself is readonly + ? entrySet = (Set) Collections.unmodifiableSet(delegate.entrySet()) + : es; + } + + @SuppressWarnings("unchecked") + @Override public Iterator iterator() { + // Safe because the returned Iterator is made unmodifiable + return (Iterator) Iterators.unmodifiableIterator(delegate.iterator()); + } + + @Override public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override public int add(E element, int occurences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection elementsToAdd) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object element) { + throw new UnsupportedOperationException(); + } + + @Override public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean removeAll(Collection elementsToRemove) { + throw new UnsupportedOperationException(); + } + + @Override public boolean retainAll(Collection elementsToRetain) { + throw new UnsupportedOperationException(); + } + + @Override public void clear() { + throw new UnsupportedOperationException(); + } + + @Override public int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + @Override public boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable view of the specified sorted multiset. Query + * operations on the returned multiset "read through" to the specified + * multiset, and attempts to modify the returned multiset result in an {@link + * UnsupportedOperationException}. + * + *

The returned multiset will be serializable if the specified multiset is + * serializable. + * + * @param sortedMultiset the sorted multiset for which an unmodifiable view is + * to be generated + * @return an unmodifiable view of the multiset + * @since 11.0 + */ + @Beta + public static SortedMultiset unmodifiableSortedMultiset( + SortedMultiset sortedMultiset) { + return new UnmodifiableSortedMultiset(checkNotNull(sortedMultiset)); + } + + private static final class UnmodifiableSortedMultiset + extends UnmodifiableMultiset implements SortedMultiset { + private UnmodifiableSortedMultiset(SortedMultiset delegate) { + super(delegate); + } + + @Override + protected SortedMultiset delegate() { + return (SortedMultiset) super.delegate(); + } + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + SortedSet createElementSet() { + return Collections.unmodifiableSortedSet(delegate().elementSet()); + } + + @Override + public SortedSet elementSet() { + return (SortedSet) super.elementSet(); + } + + private transient UnmodifiableSortedMultiset descendingMultiset; + + @Override + public SortedMultiset descendingMultiset() { + UnmodifiableSortedMultiset result = descendingMultiset; + if (result == null) { + result = new UnmodifiableSortedMultiset( + delegate().descendingMultiset()); + result.descendingMultiset = this; + return descendingMultiset = result; + } + return result; + } + + @Override + public Entry firstEntry() { + return delegate().firstEntry(); + } + + @Override + public Entry lastEntry() { + return delegate().lastEntry(); + } + + @Override + public Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + return unmodifiableSortedMultiset( + delegate().headMultiset(upperBound, boundType)); + } + + @Override + public SortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, + E upperBound, BoundType upperBoundType) { + return unmodifiableSortedMultiset(delegate().subMultiset( + lowerBound, lowerBoundType, upperBound, upperBoundType)); + } + + @Override + public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return unmodifiableSortedMultiset( + delegate().tailMultiset(lowerBound, boundType)); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an immutable multiset entry with the specified element and count. + * The entry will be serializable if {@code e} is. + * + * @param e the element to be associated with the returned entry + * @param n the count to be associated with the returned entry + * @throws IllegalArgumentException if {@code n} is negative + */ + public static Multiset.Entry immutableEntry(@Nullable E e, int n) { + return new ImmutableEntry(e, n); + } + + static final class ImmutableEntry extends AbstractEntry implements + Serializable { + @Nullable final E element; + final int count; + + ImmutableEntry(@Nullable E element, int count) { + this.element = element; + this.count = count; + checkArgument(count >= 0); + } + + @Override + @Nullable public E getElement() { + return element; + } + + @Override + public int getCount() { + return count; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a multiset view of the specified set. The multiset is backed by the + * set, so changes to the set are reflected in the multiset, and vice versa. + * If the set is modified while an iteration over the multiset is in progress + * (except through the iterator's own {@code remove} operation) the results of + * the iteration are undefined. + * + *

The multiset supports element removal, which removes the corresponding + * element from the set. It does not support the {@code add} or {@code addAll} + * operations, nor does it support the use of {@code setCount} to add + * elements. + * + *

The returned multiset will be serializable if the specified set is + * serializable. The multiset is threadsafe if the set is threadsafe. + * + * @param set the backing set for the returned multiset view + */ + static Multiset forSet(Set set) { + return new SetMultiset(set); + } + + /** @see Multisets#forSet */ + private static class SetMultiset extends ForwardingCollection + implements Multiset, Serializable { + final Set delegate; + + SetMultiset(Set set) { + delegate = checkNotNull(set); + } + + @Override protected Set delegate() { + return delegate; + } + + @Override + public int count(Object element) { + return delegate.contains(element) ? 1 : 0; + } + + @Override + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override + public int remove(Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0); + return delegate.remove(element) ? 1 : 0; + } + + transient Set elementSet; + + @Override + public Set elementSet() { + Set es = elementSet; + return (es == null) ? elementSet = new ElementSet() : es; + } + + transient Set> entrySet; + + @Override public Set> entrySet() { + Set> es = entrySet; + if (es == null) { + es = entrySet = new EntrySet() { + @Override Multiset multiset() { + return SetMultiset.this; + } + + @Override public Iterator> iterator() { + return new TransformedIterator>(delegate.iterator()) { + @Override + Entry transform(E e) { + return Multisets.immutableEntry(e, 1); + } + }; + } + + @Override public int size() { + return delegate.size(); + } + }; + } + return es; + } + + @Override public boolean add(E o) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public int setCount(E element, int count) { + checkNonnegative(count, "count"); + + if (count == count(element)) { + return count; + } else if (count == 0) { + remove(element); + return 1; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + return this.size() == that.size() && delegate.equals(that.elementSet()); + } + return false; + } + + @Override public int hashCode() { + int sum = 0; + for (E e : this) { + sum += ((e == null) ? 0 : e.hashCode()) ^ 1; + } + return sum; + } + + /** @see SetMultiset#elementSet */ + class ElementSet extends ForwardingSet { + @Override protected Set delegate() { + return delegate; + } + + @Override public boolean add(E o) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the expected number of distinct elements given the specified + * elements. The number of distinct elements is only computed if {@code + * elements} is an instance of {@code Multiset}; otherwise the default value + * of 11 is returned. + */ + static int inferDistinctElements(Iterable elements) { + if (elements instanceof Multiset) { + return ((Multiset) elements).elementSet().size(); + } + return 11; // initial capacity will be rounded up to 16 + } + + /** + * Returns an unmodifiable view of the intersection of two multisets. + * An element's count in the multiset is the smaller of its counts in the two + * backing multisets. The iteration order of the returned multiset matches the + * element set of {@code multiset1}, with repeated occurrences of the same + * element appearing consecutively. + * + *

Results are undefined if {@code multiset1} and {@code multiset2} are + * based on different equivalence relations (as {@code HashMultiset} and + * {@code TreeMultiset} are). + * + * @since 2.0 + */ + public static Multiset intersection( + final Multiset multiset1, final Multiset multiset2) { + checkNotNull(multiset1); + checkNotNull(multiset2); + + return new AbstractMultiset() { + @Override + public int count(Object element) { + int count1 = multiset1.count(element); + return (count1 == 0) ? 0 : Math.min(count1, multiset2.count(element)); + } + + @Override + Set createElementSet() { + return Sets.intersection( + multiset1.elementSet(), multiset2.elementSet()); + } + + @Override + Iterator> entryIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + return new AbstractIterator>() { + @Override + protected Entry computeNext() { + while (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + int count = Math.min(entry1.getCount(), multiset2.count(element)); + if (count > 0) { + return Multisets.immutableEntry(element, count); + } + } + return endOfData(); + } + }; + } + + @Override + int distinctElements() { + return elementSet().size(); + } + }; + } + + /** + * Returns {@code true} if {@code subMultiset.count(o) <= + * superMultiset.count(o)} for all {@code o}. + * + * @since 10.0 + */ + public static boolean containsOccurrences( + Multiset superMultiset, Multiset subMultiset) { + checkNotNull(superMultiset); + checkNotNull(subMultiset); + for (Entry entry : subMultiset.entrySet()) { + int superCount = superMultiset.count(entry.getElement()); + if (superCount < entry.getCount()) { + return false; + } + } + return true; + } + + /** + * Modifies {@code multisetToModify} so that its count for an element + * {@code e} is at most {@code multisetToRetain.count(e)}. + * + *

To be precise, {@code multisetToModify.count(e)} is set to + * {@code Math.min(multisetToModify.count(e), + * multisetToRetain.count(e))}. This is similar to + * {@link #intersection(Multiset, Multiset) intersection} + * {@code (multisetToModify, multisetToRetain)}, but mutates + * {@code multisetToModify} instead of returning a view. + * + *

In contrast, {@code multisetToModify.retainAll(multisetToRetain)} keeps + * all occurrences of elements that appear at all in {@code + * multisetToRetain}, and deletes all occurrences of all other elements. + * + * @return {@code true} if {@code multisetToModify} was changed as a result + * of this operation + * @since 10.0 + */ + public static boolean retainOccurrences(Multiset multisetToModify, + Multiset multisetToRetain) { + return retainOccurrencesImpl(multisetToModify, multisetToRetain); + } + + /** + * Delegate implementation which cares about the element type. + */ + private static boolean retainOccurrencesImpl( + Multiset multisetToModify, Multiset occurrencesToRetain) { + checkNotNull(multisetToModify); + checkNotNull(occurrencesToRetain); + // Avoiding ConcurrentModificationExceptions is tricky. + Iterator> entryIterator = multisetToModify.entrySet().iterator(); + boolean changed = false; + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + int retainCount = occurrencesToRetain.count(entry.getElement()); + if (retainCount == 0) { + entryIterator.remove(); + changed = true; + } else if (retainCount < entry.getCount()) { + multisetToModify.setCount(entry.getElement(), retainCount); + changed = true; + } + } + return changed; + } + + /** + * For each occurrence of an element {@code e} in {@code occurrencesToRemove}, + * removes one occurrence of {@code e} in {@code multisetToModify}. + * + *

Equivalently, this method modifies {@code multisetToModify} so that + * {@code multisetToModify.count(e)} is set to + * {@code Math.max(0, multisetToModify.count(e) - + * occurrencesToRemove.count(e))}. + * + *

This is not the same as {@code multisetToModify.} + * {@link Multiset#removeAll removeAll}{@code (occurrencesToRemove)}, which + * removes all occurrences of elements that appear in + * {@code occurrencesToRemove}. However, this operation is equivalent + * to, albeit more efficient than, the following:

   {@code
+   *
+   *   for (E e : occurrencesToRemove) {
+   *     multisetToModify.remove(e);
+   *   }}
+ * + * @return {@code true} if {@code multisetToModify} was changed as a result of + * this operation + * @since 10.0 + */ + public static boolean removeOccurrences( + Multiset multisetToModify, Multiset occurrencesToRemove) { + return removeOccurrencesImpl(multisetToModify, occurrencesToRemove); + } + + /** + * Delegate that cares about the element types in occurrencesToRemove. + */ + private static boolean removeOccurrencesImpl( + Multiset multisetToModify, Multiset occurrencesToRemove) { + // TODO(user): generalize to removing an Iterable, perhaps + checkNotNull(multisetToModify); + checkNotNull(occurrencesToRemove); + + boolean changed = false; + Iterator> entryIterator = multisetToModify.entrySet().iterator(); + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + int removeCount = occurrencesToRemove.count(entry.getElement()); + if (removeCount >= entry.getCount()) { + entryIterator.remove(); + changed = true; + } else if (removeCount > 0) { + multisetToModify.remove(entry.getElement(), removeCount); + changed = true; + } + } + return changed; + } + + /** + * Implementation of the {@code equals}, {@code hashCode}, and + * {@code toString} methods of {@link Multiset.Entry}. + */ + abstract static class AbstractEntry implements Multiset.Entry { + /** + * Indicates whether an object equals this entry, following the behavior + * specified in {@link Multiset.Entry#equals}. + */ + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry that = (Multiset.Entry) object; + return this.getCount() == that.getCount() + && Objects.equal(this.getElement(), that.getElement()); + } + return false; + } + + /** + * Return this entry's hash code, following the behavior specified in + * {@link Multiset.Entry#hashCode}. + */ + @Override public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string + * representation consists of the associated element if the associated count + * is one, and otherwise the associated element followed by the characters + * " x " (space, x and space) followed by the count. Elements and counts are + * converted to strings as by {@code String.valueOf}. + */ + @Override public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } + } + + /** + * An implementation of {@link Multiset#equals}. + */ + static boolean equalsImpl(Multiset multiset, @Nullable Object object) { + if (object == multiset) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + /* + * We can't simply check whether the entry sets are equal, since that + * approach fails when a TreeMultiset has a comparator that returns 0 + * when passed unequal elements. + */ + + if (multiset.size() != that.size() + || multiset.entrySet().size() != that.entrySet().size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (multiset.count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + /** + * An implementation of {@link Multiset#addAll}. + */ + static boolean addAllImpl( + Multiset self, Collection elements) { + if (elements.isEmpty()) { + return false; + } + if (elements instanceof Multiset) { + Multiset that = cast(elements); + for (Entry entry : that.entrySet()) { + self.add(entry.getElement(), entry.getCount()); + } + } else { + Iterators.addAll(self, elements.iterator()); + } + return true; + } + + /** + * An implementation of {@link Multiset#removeAll}. + */ + static boolean removeAllImpl( + Multiset self, Collection elementsToRemove) { + Collection collection = (elementsToRemove instanceof Multiset) + ? ((Multiset) elementsToRemove).elementSet() : elementsToRemove; + + return self.elementSet().removeAll(collection); + } + + /** + * An implementation of {@link Multiset#retainAll}. + */ + static boolean retainAllImpl( + Multiset self, Collection elementsToRetain) { + checkNotNull(elementsToRetain); + Collection collection = (elementsToRetain instanceof Multiset) + ? ((Multiset) elementsToRetain).elementSet() : elementsToRetain; + + return self.elementSet().retainAll(collection); + } + + /** + * An implementation of {@link Multiset#setCount(Object, int)}. + */ + static int setCountImpl(Multiset self, E element, int count) { + checkNonnegative(count, "count"); + + int oldCount = self.count(element); + + int delta = count - oldCount; + if (delta > 0) { + self.add(element, delta); + } else if (delta < 0) { + self.remove(element, -delta); + } + + return oldCount; + } + + /** + * An implementation of {@link Multiset#setCount(Object, int, int)}. + */ + static boolean setCountImpl( + Multiset self, E element, int oldCount, int newCount) { + checkNonnegative(oldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + if (self.count(element) == oldCount) { + self.setCount(element, newCount); + return true; + } else { + return false; + } + } + + abstract static class ElementSet extends Sets.ImprovedAbstractSet { + abstract Multiset multiset(); + + @Override public void clear() { + multiset().clear(); + } + + @Override public boolean contains(Object o) { + return multiset().contains(o); + } + + @Override public boolean containsAll(Collection c) { + return multiset().containsAll(c); + } + + @Override public boolean isEmpty() { + return multiset().isEmpty(); + } + + @Override public Iterator iterator() { + return new TransformedIterator, E>(multiset().entrySet().iterator()) { + @Override + E transform(Entry entry) { + return entry.getElement(); + } + }; + } + + @Override + public boolean remove(Object o) { + int count = multiset().count(o); + if (count > 0) { + multiset().remove(o, count); + return true; + } + return false; + } + + @Override public int size() { + return multiset().entrySet().size(); + } + } + + abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract Multiset multiset(); + + @Override public boolean contains(@Nullable Object o) { + if (o instanceof Entry) { + @SuppressWarnings("cast") + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = multiset().count(entry.getElement()); + return count == entry.getCount(); + + } + return false; + } + + @SuppressWarnings("cast") + @Override public boolean remove(Object o) { + return contains(o) + && multiset().elementSet().remove(((Entry) o).getElement()); + } + + @Override public void clear() { + multiset().clear(); + } + } + + /** + * An implementation of {@link Multiset#iterator}. + */ + static Iterator iteratorImpl(Multiset multiset) { + return new MultisetIteratorImpl( + multiset, multiset.entrySet().iterator()); + } + + static final class MultisetIteratorImpl implements Iterator { + private final Multiset multiset; + private final Iterator> entryIterator; + private Entry currentEntry; + /** Count of subsequent elements equal to current element */ + private int laterCount; + /** Count of all elements equal to current element */ + private int totalCount; + private boolean canRemove; + + MultisetIteratorImpl( + Multiset multiset, Iterator> entryIterator) { + this.multiset = multiset; + this.entryIterator = entryIterator; + } + + @Override + public boolean hasNext() { + return laterCount > 0 || entryIterator.hasNext(); + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (laterCount == 0) { + currentEntry = entryIterator.next(); + totalCount = laterCount = currentEntry.getCount(); + } + laterCount--; + canRemove = true; + return currentEntry.getElement(); + } + + @Override + public void remove() { + Iterators.checkRemove(canRemove); + if (totalCount == 1) { + entryIterator.remove(); + } else { + multiset.remove(currentEntry.getElement()); + } + totalCount--; + canRemove = false; + } + } + + /** + * An implementation of {@link Multiset#size}. + */ + static int sizeImpl(Multiset multiset) { + long size = 0; + for (Entry entry : multiset.entrySet()) { + size += entry.getCount(); + } + return Ints.saturatedCast(size); + } + + static void checkNonnegative(int count, String name) { + checkArgument(count >= 0, "%s cannot be negative: %s", name, count); + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + static Multiset cast(Iterable iterable) { + return (Multiset) iterable; + } + + private static final Ordering> DECREASING_COUNT_ORDERING = new Ordering>() { + @Override + public int compare(Entry entry1, Entry entry2) { + return Ints.compare(entry2.getCount(), entry1.getCount()); + } + }; + + /** + * Returns a copy of {@code multiset} as an {@link ImmutableMultiset} whose iteration order is + * highest count first, with ties broken by the iteration order of the original multiset. + * + * @since 11.0 + */ + @Beta + public static ImmutableMultiset copyHighestCountFirst(Multiset multiset) { + List> sortedEntries = + Multisets.DECREASING_COUNT_ORDERING.sortedCopy(multiset.entrySet()); + return ImmutableMultiset.copyFromEntries(sortedEntries); + } +} diff --git a/guava/src/com/google/common/collect/MutableClassToInstanceMap.java b/guava/src/com/google/common/collect/MutableClassToInstanceMap.java new file mode 100644 index 0000000..c723cea --- /dev/null +++ b/guava/src/com/google/common/collect/MutableClassToInstanceMap.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.collect.MapConstraints.ConstrainedMap; +import com.google.common.primitives.Primitives; + +import java.util.HashMap; +import java.util.Map; + +/** + * A mutable class-to-instance map backed by an arbitrary user-provided map. + * See also {@link ImmutableClassToInstanceMap}. + * + *

See the Guava User Guide article on + * {@code ClassToInstanceMap}. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +public final class MutableClassToInstanceMap + extends ConstrainedMap, B> + implements ClassToInstanceMap { + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a {@link + * HashMap} using the default initial capacity and load factor. + */ + public static MutableClassToInstanceMap create() { + return new MutableClassToInstanceMap( + new HashMap, B>()); + } + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a given + * empty {@code backingMap}. The caller surrenders control of the backing map, + * and thus should not allow any direct references to it to remain accessible. + */ + public static MutableClassToInstanceMap create( + Map, B> backingMap) { + return new MutableClassToInstanceMap(backingMap); + } + + private MutableClassToInstanceMap(Map, B> delegate) { + super(delegate, VALUE_CAN_BE_CAST_TO_KEY); + } + + private static final MapConstraint, Object> VALUE_CAN_BE_CAST_TO_KEY + = new MapConstraint, Object>() { + @Override + public void checkKeyValue(Class key, Object value) { + cast(key, value); + } + }; + + @Override + public T putInstance(Class type, T value) { + return cast(type, put(type, value)); + } + + @Override + public T getInstance(Class type) { + return cast(type, get(type)); + } + + private static T cast(Class type, B value) { + return Primitives.wrap(type).cast(value); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/NaturalOrdering.java b/guava/src/com/google/common/collect/NaturalOrdering.java new file mode 100644 index 0000000..8fbe0d0 --- /dev/null +++ b/guava/src/com/google/common/collect/NaturalOrdering.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** An ordering that uses the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? +final class NaturalOrdering + extends Ordering implements Serializable { + static final NaturalOrdering INSTANCE = new NaturalOrdering(); + + @Override public int compare(Comparable left, Comparable right) { + checkNotNull(left); // for GWT + checkNotNull(right); + if (left == right) { + return 0; + } + + return left.compareTo(right); + } + + @Override public Ordering reverse() { + return (Ordering) ReverseNaturalOrdering.INSTANCE; + } + + // Override to remove a level of indirection from inner loop + @Override public int binarySearch( + List sortedList, Comparable key) { + return Collections.binarySearch((List) sortedList, key); + } + + // Override to remove a level of indirection from inner loop + @Override public List sortedCopy( + Iterable iterable) { + List list = Lists.newArrayList(iterable); + Collections.sort(list); + return list; + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.natural()"; + } + + private NaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/NullsFirstOrdering.java b/guava/src/com/google/common/collect/NullsFirstOrdering.java new file mode 100644 index 0000000..2e8a3ac --- /dev/null +++ b/guava/src/com/google/common/collect/NullsFirstOrdering.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** An ordering that treats {@code null} as less than all other values. */ +@GwtCompatible(serializable = true) +final class NullsFirstOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsFirstOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override public int compare(@Nullable T left, @Nullable T right) { + if (left == right) { + return 0; + } + if (left == null) { + return RIGHT_IS_GREATER; + } + if (right == null) { + return LEFT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsLast(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override public Ordering nullsFirst() { + return (Ordering) this; + } + + @Override public Ordering nullsLast() { + return ordering.nullsLast(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsFirstOrdering) { + NullsFirstOrdering that = (NullsFirstOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return ordering.hashCode() ^ 957692532; // meaningless + } + + @Override public String toString() { + return ordering + ".nullsFirst()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/NullsLastOrdering.java b/guava/src/com/google/common/collect/NullsLastOrdering.java new file mode 100644 index 0000000..9de17bd --- /dev/null +++ b/guava/src/com/google/common/collect/NullsLastOrdering.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** An ordering that treats {@code null} as greater than all other values. */ +@GwtCompatible(serializable = true) +final class NullsLastOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsLastOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override public int compare(@Nullable T left, @Nullable T right) { + if (left == right) { + return 0; + } + if (left == null) { + return LEFT_IS_GREATER; + } + if (right == null) { + return RIGHT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsFirst(); + } + + @Override public Ordering nullsFirst() { + return ordering.nullsFirst(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override public Ordering nullsLast() { + return (Ordering) this; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsLastOrdering) { + NullsLastOrdering that = (NullsLastOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return ordering.hashCode() ^ -921210296; // meaningless + } + + @Override public String toString() { + return ordering + ".nullsLast()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ObjectArrays.java b/guava/src/com/google/common/collect/ObjectArrays.java new file mode 100644 index 0000000..dfd1fe9 --- /dev/null +++ b/guava/src/com/google/common/collect/ObjectArrays.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.lang.reflect.Array; +import java.util.Collection; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to object arrays. + * + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class ObjectArrays { + static final Object[] EMPTY_ARRAY = new Object[0]; + + private ObjectArrays() {} + + /** + * Returns a new array of the given length with the specified component type. + * + * @param type the component type + * @param length the length of the new array + */ + @GwtIncompatible("Array.newInstance(Class, int)") + @SuppressWarnings("unchecked") + public static T[] newArray(Class type, int length) { + return (T[]) Array.newInstance(type, length); + } + + /** + * Returns a new array of the given length with the same type as a reference + * array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + public static T[] newArray(T[] reference, int length) { + return Platform.newArray(reference, length); + } + + /** + * Returns a new array that contains the concatenated contents of two arrays. + * + * @param first the first array of elements to concatenate + * @param second the second array of elements to concatenate + * @param type the component type of the returned array + */ + @GwtIncompatible("Array.newInstance(Class, int)") + public static T[] concat(T[] first, T[] second, Class type) { + T[] result = newArray(type, first.length + second.length); + System.arraycopy(first, 0, result, 0, first.length); + System.arraycopy(second, 0, result, first.length, second.length); + return result; + } + + /** + * Returns a new array that prepends {@code element} to {@code array}. + * + * @param element the element to prepend to the front of {@code array} + * @param array the array of elements to append + * @return an array whose size is one larger than {@code array}, with + * {@code element} occupying the first position, and the + * elements of {@code array} occupying the remaining elements. + */ + public static T[] concat(@Nullable T element, T[] array) { + T[] result = newArray(array, array.length + 1); + result[0] = element; + System.arraycopy(array, 0, result, 1, array.length); + return result; + } + + /** + * Returns a new array that appends {@code element} to {@code array}. + * + * @param array the array of elements to prepend + * @param element the element to append to the end + * @return an array whose size is one larger than {@code array}, with + * the same contents as {@code array}, plus {@code element} occupying the + * last position. + */ + public static T[] concat(T[] array, @Nullable T element) { + T[] result = arraysCopyOf(array, array.length + 1); + result[array.length] = element; + return result; + } + + /** GWT safe version of Arrays.copyOf. */ + static T[] arraysCopyOf(T[] original, int newLength) { + T[] copy = newArray(original, newLength); + System.arraycopy( + original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * Returns an array containing all of the elements in the specified + * collection; the runtime type of the returned array is that of the specified + * array. If the collection 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 specified collection. + * + *

If the collection fits in the specified array with room to spare (i.e., + * the array has more elements than the collection), the element in the array + * immediately following the end of the collection is set to {@code null}. + * This is useful in determining the length of the collection only if + * the caller knows that the collection does not contain any null elements. + * + *

This method returns the elements in the order they are returned by the + * collection's iterator. + * + *

TODO(kevinb): support concurrently modified collections? + * + * @param c the collection for which to return an array of elements + * @param array the array in which to place the collection elements + * @throws ArrayStoreException if the runtime type of the specified array is + * not a supertype of the runtime type of every element in the specified + * collection + */ + static T[] toArrayImpl(Collection c, T[] array) { + int size = c.size(); + if (array.length < size) { + array = newArray(array, size); + } + fillArray(c, array); + if (array.length > size) { + array[size] = null; + } + return array; + } + + /** + * Returns an array containing all of the elements in the specified + * collection. This method returns the elements in the order they are returned + * by the collection's iterator. The returned array is "safe" in that no + * references to it are maintained by the collection. The caller is thus free + * to modify the returned array. + * + *

This method assumes that the collection size doesn't change while the + * method is running. + * + *

TODO(kevinb): support concurrently modified collections? + * + * @param c the collection for which to return an array of elements + */ + static Object[] toArrayImpl(Collection c) { + return fillArray(c, new Object[c.size()]); + } + + private static Object[] fillArray(Iterable elements, Object[] array) { + int i = 0; + for (Object element : elements) { + array[i++] = element; + } + return array; + } + + /** + * Swaps {@code array[i]} with {@code array[j]}. + */ + static void swap(Object[] array, int i, int j) { + Object temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + + // We do this instead of Preconditions.checkNotNull to save boxing and array + // creation cost. + static Object checkElementNotNull(Object element, int index) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + return element; + } +} diff --git a/guava/src/com/google/common/collect/Ordering.java b/guava/src/com/google/common/collect/Ordering.java new file mode 100644 index 0000000..3530765 --- /dev/null +++ b/guava/src/com/google/common/collect/Ordering.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * A comparator, with additional methods to support common operations. This is + * an "enriched" version of {@code Comparator}, in the same sense that {@link + * FluentIterable} is an enriched {@link Iterable}). For example:

   {@code
+ *
+ *   if (Ordering.from(comparator).reverse().isOrdered(list)) { ... }}
+ * + * The {@link #from(Comparator)} method returns the equivalent {@code Ordering} + * instance for a pre-existing comparator. You can also skip the comparator step + * and extend {@code Ordering} directly:
   {@code
+ *
+ *   Ordering byLengthOrdering = new Ordering() {
+ *     public int compare(String left, String right) {
+ *       return Ints.compare(left.length(), right.length());
+ *     }
+ *   };}
+ * + * Except as noted, the orderings returned by the factory methods of this + * class are serializable if and only if the provided instances that back them + * are. For example, if {@code ordering} and {@code function} can themselves be + * serialized, then {@code ordering.onResultOf(function)} can as well. + * + *

See the Guava User Guide article on + * {@code Ordering}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class Ordering implements Comparator { + // Natural order + + /** + * Returns a serializable ordering that uses the natural order of the values. + * The ordering throws a {@link NullPointerException} when passed a null + * parameter. + * + *

The type specification is {@code }, instead of + * the technically correct {@code >}, to + * support legacy types from before Java 5. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") // TODO(kevinb): right way to explain this?? + public static Ordering natural() { + return (Ordering) NaturalOrdering.INSTANCE; + } + + // Static factories + + /** + * Returns an ordering based on an existing comparator instance. Note + * that there's no need to create a new comparator just to pass it in + * here; simply subclass {@code Ordering} and implement its {@code compareTo} + * method directly instead. + * + * @param comparator the comparator that defines the order + * @return comparator itself if it is already an {@code Ordering}; otherwise + * an ordering that wraps that comparator + */ + @GwtCompatible(serializable = true) + public static Ordering from(Comparator comparator) { + return (comparator instanceof Ordering) + ? (Ordering) comparator + : new ComparatorOrdering(comparator); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @GwtCompatible(serializable = true) + @Deprecated public static Ordering from(Ordering ordering) { + return checkNotNull(ordering); + } + + /** + * Returns an ordering that compares objects according to the order in + * which they appear in the given list. Only objects present in the list + * (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Subsequent changes + * to the {@code valuesInOrder} list will have no effect on the returned + * comparator. Null values in the list are not supported. + * + *

The returned comparator throws an {@link ClassCastException} when it + * receives an input parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are + * serializable. + * + * @param valuesInOrder the values that the returned comparator will be able + * to compare, in the order the comparator should induce + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if {@code valuesInOrder} contains any + * duplicate values (according to {@link Object#equals}) + */ + @GwtCompatible(serializable = true) + public static Ordering explicit(List valuesInOrder) { + return new ExplicitOrdering(valuesInOrder); + } + + /** + * Returns an ordering that compares objects according to the order in + * which they are given to this method. Only objects present in the argument + * list (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Null values in the + * argument list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it + * receives an input parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are + * serializable. + * + * @param leastValue the value which the returned comparator should consider + * the "least" of all values + * @param remainingValuesInOrder the rest of the values that the returned + * comparator will be able to compare, in the order the comparator should + * follow + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if any duplicate values (according to + * {@link Object#equals(Object)}) are present among the method arguments + */ + @GwtCompatible(serializable = true) + public static Ordering explicit( + T leastValue, T... remainingValuesInOrder) { + return explicit(Lists.asList(leastValue, remainingValuesInOrder)); + } + + // Ordering singletons + + /** + * Returns an ordering which treats all values as equal, indicating "no + * ordering." Passing this ordering to any stable sort algorithm + * results in no change to the order of elements. Note especially that {@link + * #sortedCopy} and {@link #immutableSortedCopy} are stable, and in the + * returned instance these are implemented by simply copying the source list. + * + *

Example:

   {@code
+   *
+   *   Ordering.allEqual().nullsLast().sortedCopy(
+   *       asList(t, null, e, s, null, t, null))}
+ * + * Assuming {@code t}, {@code e} and {@code s} are non-null, this returns + * {@code [t, e, s, t, null, null, null]} regardlesss of the true comparison + * order of those three values (which might not even implement {@link + * Comparable} at all). + * + *

Warning: by definition, this comparator is not consistent with + * equals (as defined {@linkplain Comparator here}). Avoid its use in + * APIs, such as {@link TreeSet#TreeSet(Comparator)}, where such consistency + * is expected. + * + *

The returned comparator is serializable. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") + public static Ordering allEqual() { + return AllEqualOrdering.INSTANCE; + } + + /** + * Returns an ordering that compares objects by the natural ordering of their + * string representations as returned by {@code toString()}. It does not + * support null values. + * + *

The comparator is serializable. + */ + @GwtCompatible(serializable = true) + public static Ordering usingToString() { + return UsingToStringOrdering.INSTANCE; + } + + /** + * Returns an arbitrary ordering over all objects, for which {@code compare(a, + * b) == 0} implies {@code a == b} (identity equality). There is no meaning + * whatsoever to the order imposed, but it is constant for the life of the VM. + * + *

Because the ordering is identity-based, it is not "consistent with + * {@link Object#equals(Object)}" as defined by {@link Comparator}. Use + * caution when building a {@link SortedSet} or {@link SortedMap} from it, as + * the resulting collection will not behave exactly according to spec. + * + *

This ordering is not serializable, as its implementation relies on + * {@link System#identityHashCode(Object)}, so its behavior cannot be + * preserved across serialization. + * + * @since 2.0 + */ + public static Ordering arbitrary() { + return ArbitraryOrderingHolder.ARBITRARY_ORDERING; + } + + private static class ArbitraryOrderingHolder { + static final Ordering ARBITRARY_ORDERING = new ArbitraryOrdering(); + } + + @VisibleForTesting static class ArbitraryOrdering extends Ordering { + @SuppressWarnings("deprecation") // TODO(kevinb): ? + private Map uids = + Platform.tryWeakKeys(new MapMaker()).makeComputingMap( + new Function() { + final AtomicInteger counter = new AtomicInteger(0); + @Override + public Integer apply(Object from) { + return counter.getAndIncrement(); + } + }); + + @Override public int compare(Object left, Object right) { + if (left == right) { + return 0; + } else if (left == null) { + return -1; + } else if (right == null) { + return 1; + } + int leftCode = identityHashCode(left); + int rightCode = identityHashCode(right); + if (leftCode != rightCode) { + return leftCode < rightCode ? -1 : 1; + } + + // identityHashCode collision (rare, but not as rare as you'd think) + int result = uids.get(left).compareTo(uids.get(right)); + if (result == 0) { + throw new AssertionError(); // extremely, extremely unlikely. + } + return result; + } + + @Override public String toString() { + return "Ordering.arbitrary()"; + } + + /* + * We need to be able to mock identityHashCode() calls for tests, because it + * can take 1-10 seconds to find colliding objects. Mocking frameworks that + * can do magic to mock static method calls still can't do so for a system + * class, so we need the indirection. In production, Hotspot should still + * recognize that the call is 1-morphic and should still be willing to + * inline it if necessary. + */ + int identityHashCode(Object object) { + return System.identityHashCode(object); + } + } + + // Constructor + + /** + * Constructs a new instance of this class (only invokable by the subclass + * constructor, typically implicit). + */ + protected Ordering() {} + + // Instance-based factories (and any static equivalents) + + /** + * Returns the reverse of this ordering; the {@code Ordering} equivalent to + * {@link Collections#reverseOrder(Comparator)}. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().reverse(); + @GwtCompatible(serializable = true) + public Ordering reverse() { + return new ReverseOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as less than all other values + * and uses {@code this} to compare non-null values. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsFirst(); + @GwtCompatible(serializable = true) + public Ordering nullsFirst() { + return new NullsFirstOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as greater than all other + * values and uses this ordering to compare non-null values. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsLast(); + @GwtCompatible(serializable = true) + public Ordering nullsLast() { + return new NullsLastOrdering(this); + } + + /** + * Returns a new ordering on {@code F} which orders elements by first applying + * a function to them, then comparing those results using {@code this}. For + * example, to compare objects by their string forms, in a case-insensitive + * manner, use:
   {@code
+   *
+   *   Ordering.from(String.CASE_INSENSITIVE_ORDER)
+   *       .onResultOf(Functions.toStringFunction())}
+ */ + @GwtCompatible(serializable = true) + public Ordering onResultOf(Function function) { + return new ByFunctionOrdering(function, this); + } + + /** + * Returns an ordering which first uses the ordering {@code this}, but which + * in the event of a "tie", then delegates to {@code secondaryComparator}. + * For example, to sort a bug list first by status and second by priority, you + * might use {@code byStatus.compound(byPriority)}. For a compound ordering + * with three or more components, simply chain multiple calls to this method. + * + *

An ordering produced by this method, or a chain of calls to this method, + * is equivalent to one created using {@link Ordering#compound(Iterable)} on + * the same component comparators. + */ + @GwtCompatible(serializable = true) + public Ordering compound( + Comparator secondaryComparator) { + return new CompoundOrdering(this, checkNotNull(secondaryComparator)); + } + + /** + * Returns an ordering which tries each given comparator in order until a + * non-zero result is found, returning that result, and returning zero only if + * all comparators return zero. The returned ordering is based on the state of + * the {@code comparators} iterable at the time it was provided to this + * method. + * + *

The returned ordering is equivalent to that produced using {@code + * Ordering.from(comp1).compound(comp2).compound(comp3) . . .}. + * + *

Warning: Supplying an argument with undefined iteration order, + * such as a {@link HashSet}, will produce non-deterministic results. + * + * @param comparators the comparators to try in order + */ + @GwtCompatible(serializable = true) + public static Ordering compound( + Iterable> comparators) { + return new CompoundOrdering(comparators); + } + + /** + * Returns a new ordering which sorts iterables by comparing corresponding + * elements pairwise until a nonzero result is found; imposes "dictionary + * order". If the end of one iterable is reached, but not the other, the + * shorter iterable is considered to be less than the longer one. For example, + * a lexicographical natural ordering over integers considers {@code + * [] < [1] < [1, 1] < [1, 2] < [2]}. + * + *

Note that {@code ordering.lexicographical().reverse()} is not + * equivalent to {@code ordering.reverse().lexicographical()} (consider how + * each would order {@code [1]} and {@code [1, 1]}). + * + * @since 2.0 + */ + @GwtCompatible(serializable = true) + // type parameter lets us avoid the extra in statements like: + // Ordering> o = + // Ordering.natural().lexicographical(); + public Ordering> lexicographical() { + /* + * Note that technically the returned ordering should be capable of + * handling not just {@code Iterable} instances, but also any {@code + * Iterable}. However, the need for this comes up so rarely + * that it doesn't justify making everyone else deal with the very ugly + * wildcard. + */ + return new LexicographicalOrdering(this); + } + + // Regular instance methods + + // Override to add @Nullable + @Override public abstract int compare(@Nullable T left, @Nullable T right); + + /** + * Returns the least of the specified values according to this ordering. If + * there are multiple least values, the first of those is returned. The + * iterator will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @param iterator the iterator whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + * + * @since 11.0 + */ + @Beta + public E min(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E minSoFar = iterator.next(); + + while (iterator.hasNext()) { + minSoFar = min(minSoFar, iterator.next()); + } + + return minSoFar; + } + + /** + * Returns the least of the specified values according to this ordering. If + * there are multiple least values, the first of those is returned. + * + * @param iterable the iterable whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min(Iterable iterable) { + return min(iterable.iterator()); + } + + /** + * Returns the lesser of the two values according to this ordering. If the + * values compare as 0, the first is returned. + * + *

Implementation note: this method is invoked by the default + * implementations of the other {@code min} overloads, so overriding it will + * affect their behavior. + * + * @param a value to compare, returned if less than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min(@Nullable E a, @Nullable E b) { + return compare(a, b) <= 0 ? a : b; + } + + /** + * Returns the least of the specified values according to this ordering. If + * there are multiple least values, the first of those is returned. + * + * @param a value to compare, returned if less than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min( + @Nullable E a, @Nullable E b, @Nullable E c, E... rest) { + E minSoFar = min(min(a, b), c); + + for (E r : rest) { + minSoFar = min(minSoFar, r); + } + + return minSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If + * there are multiple greatest values, the first of those is returned. The + * iterator will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @param iterator the iterator whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + * + * @since 11.0 + */ + @Beta + public E max(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E maxSoFar = iterator.next(); + + while (iterator.hasNext()) { + maxSoFar = max(maxSoFar, iterator.next()); + } + + return maxSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If + * there are multiple greatest values, the first of those is returned. + * + * @param iterable the iterable whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max(Iterable iterable) { + return max(iterable.iterator()); + } + + /** + * Returns the greater of the two values according to this ordering. If the + * values compare as 0, the first is returned. + * + *

Implementation note: this method is invoked by the default + * implementations of the other {@code max} overloads, so overriding it will + * affect their behavior. + * + * @param a value to compare, returned if greater than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max(@Nullable E a, @Nullable E b) { + return compare(a, b) >= 0 ? a : b; + } + + /** + * Returns the greatest of the specified values according to this ordering. If + * there are multiple greatest values, the first of those is returned. + * + * @param a value to compare, returned if greater than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max( + @Nullable E a, @Nullable E b, @Nullable E c, E... rest) { + E maxSoFar = max(max(a, b), c); + + for (E r : rest) { + maxSoFar = max(maxSoFar, r); + } + + return maxSoFar; + } + + /** + * Returns the {@code k} least elements of the given iterable according to + * this ordering, in order from least to greatest. If there are fewer than + * {@code k} elements present, all will be included. + * + *

The implementation does not necessarily use a stable sorting + * algorithm; when multiple elements are equivalent, it is undefined which + * will come first. + * + * @return an immutable {@code RandomAccess} list of the {@code k} least + * elements in ascending order + * @throws IllegalArgumentException if {@code k} is negative + * @since 8.0 + */ + @Beta + public List leastOf(Iterable iterable, int k) { + checkArgument(k >= 0, "%d is negative", k); + + // values is not an E[], but we use it as such for readability. Hack. + @SuppressWarnings("unchecked") + E[] values = (E[]) Iterables.toArray(iterable); + + // TODO(nshupe): also sort whole list if k is *near* values.length? + // TODO(kevinb): benchmark this impl against hand-coded heap + E[] resultArray; + if (values.length <= k) { + Arrays.sort(values, this); + resultArray = values; + } else { + quicksortLeastK(values, 0, values.length - 1, k); + + // this is not an E[], but we use it as such for readability. Hack. + @SuppressWarnings("unchecked") + E[] tmp = (E[]) new Object[k]; + resultArray = tmp; + System.arraycopy(values, 0, resultArray, 0, k); + } + + return Collections.unmodifiableList(Arrays.asList(resultArray)); + } + + /** + * Returns the {@code k} greatest elements of the given iterable according to + * this ordering, in order from greatest to least. If there are fewer than + * {@code k} elements present, all will be included. + * + *

The implementation does not necessarily use a stable sorting + * algorithm; when multiple elements are equivalent, it is undefined which + * will come first. + * + * @return an immutable {@code RandomAccess} list of the {@code k} greatest + * elements in descending order + * @throws IllegalArgumentException if {@code k} is negative + * @since 8.0 + */ + @Beta + public List greatestOf(Iterable iterable, int k) { + // TODO(kevinb): see if delegation is hurting performance noticeably + // TODO(kevinb): if we change this implementation, add full unit tests. + return reverse().leastOf(iterable, k); + } + + private void quicksortLeastK( + E[] values, int left, int right, int k) { + if (right > left) { + int pivotIndex = (left + right) >>> 1; // left + ((right - left) / 2) + int pivotNewIndex = partition(values, left, right, pivotIndex); + quicksortLeastK(values, left, pivotNewIndex - 1, k); + if (pivotNewIndex < k) { + quicksortLeastK(values, pivotNewIndex + 1, right, k); + } + } + } + + private int partition( + E[] values, int left, int right, int pivotIndex) { + E pivotValue = values[pivotIndex]; + + values[pivotIndex] = values[right]; + values[right] = pivotValue; + + int storeIndex = left; + for (int i = left; i < right; i++) { + if (compare(values[i], pivotValue) < 0) { + ObjectArrays.swap(values, storeIndex, i); + storeIndex++; + } + } + ObjectArrays.swap(values, right, storeIndex); + return storeIndex; + } + + /** + * Returns a copy of the given iterable sorted by this ordering. The input is + * not modified. The returned list is modifiable, serializable, and has random + * access. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard + * elements that are duplicates according to the comparator. The sort + * performed is stable, meaning that such elements will appear in the + * resulting list in the same order they appeared in the input. + * + * @param iterable the elements to be copied and sorted + * @return a new list containing the given elements in sorted order + */ + public List sortedCopy(Iterable iterable) { + @SuppressWarnings("unchecked") // does not escape, and contains only E's + E[] array = (E[]) Iterables.toArray(iterable); + Arrays.sort(array, this); + return Lists.newArrayList(Arrays.asList(array)); + } + + /** + * Returns an immutable copy of the given iterable sorted by this + * ordering. The input is not modified. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard + * elements that are duplicates according to the comparator. The sort + * performed is stable, meaning that such elements will appear in the + * resulting list in the same order they appeared in the input. + * + * @param iterable the elements to be copied and sorted + * @return a new immutable list containing the given elements in sorted order + * @throws NullPointerException if {@code iterable} or any of its elements is + * null + * @since 3.0 + */ + public ImmutableList immutableSortedCopy( + Iterable iterable) { + @SuppressWarnings("unchecked") // we'll only ever have E's in here + E[] elements = (E[]) Iterables.toArray(iterable); + for (E e : elements) { + checkNotNull(e); + } + Arrays.sort(elements, this); + return ImmutableList.asImmutableList(elements); + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is + * greater than or equal to the element that preceded it, according to this + * ordering. Note that this is always true when the iterable has fewer than + * two elements. + */ + public boolean isOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) > 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is + * strictly greater than the element that preceded it, according to + * this ordering. Note that this is always true when the iterable has fewer + * than two elements. + */ + public boolean isStrictlyOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) >= 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * {@link Collections#binarySearch(List, Object, Comparator) Searches} + * {@code sortedList} for {@code key} using the binary search algorithm. The + * list must be sorted using this ordering. + * + * @param sortedList the list to be searched + * @param key the key to be searched for + */ + public int binarySearch(List sortedList, @Nullable T key) { + return Collections.binarySearch(sortedList, key, this); + } + + /** + * Exception thrown by a {@link Ordering#explicit(List)} or {@link + * Ordering#explicit(Object, Object[])} comparator when comparing a value + * outside the set of values it can compare. Extending {@link + * ClassCastException} may seem odd, but it is required. + */ + // TODO(kevinb): make this public, document it right + @VisibleForTesting + static class IncomparableValueException extends ClassCastException { + final Object value; + + IncomparableValueException(Object value) { + super("Cannot compare value: " + value); + this.value = value; + } + + private static final long serialVersionUID = 0; + } + + // Never make these public + static final int LEFT_IS_GREATER = 1; + static final int RIGHT_IS_GREATER = -1; +} diff --git a/guava/src/com/google/common/collect/PeekingIterator.java b/guava/src/com/google/common/collect/PeekingIterator.java new file mode 100644 index 0000000..294b2e6 --- /dev/null +++ b/guava/src/com/google/common/collect/PeekingIterator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An iterator that supports a one-element lookahead while iterating. + * + *

See the Guava User Guide article on + * {@code PeekingIterator}. + * + * @author Mick Killianey + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface PeekingIterator extends Iterator { + /** + * Returns the next element in the iteration, without advancing the iteration. + * + *

Calls to {@code peek()} should not change the state of the iteration, + * except that it may prevent removal of the most recent element via + * {@link #remove()}. + * + * @throws NoSuchElementException if the iteration has no more elements + * according to {@link #hasNext()} + */ + E peek(); + + /** + * {@inheritDoc} + * + *

The objects returned by consecutive calls to {@link #peek()} then {@link + * #next()} are guaranteed to be equal to each other. + */ + @Override + E next(); + + /** + * {@inheritDoc} + * + *

Implementations may or may not support removal when a call to {@link + * #peek()} has occurred since the most recent call to {@link #next()}. + * + * @throws IllegalStateException if there has been a call to {@link #peek()} + * since the most recent call to {@link #next()} and this implementation + * does not support this sequence of calls (optional) + */ + @Override + void remove(); +} diff --git a/guava/src/com/google/common/collect/Platform.java b/guava/src/com/google/common/collect/Platform.java new file mode 100644 index 0000000..4baa666 --- /dev/null +++ b/guava/src/com/google/common/collect/Platform.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.Maps.EntryTransformer; + +import java.lang.reflect.Array; +import java.util.NavigableMap; +import java.util.SortedMap; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Hayward Chan + */ +@GwtCompatible(emulated = true) +class Platform { + /** + * Clone the given array using {@link Object#clone()}. It is factored out so + * that it can be emulated in GWT. + */ + static T[] clone(T[] array) { + return array.clone(); + } + + /** + * Returns a new array of the given length with the same type as a reference + * array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + static T[] newArray(T[] reference, int length) { + Class type = reference.getClass().getComponentType(); + + // the cast is safe because + // result.getClass() == reference.getClass().getComponentType() + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(type, length); + return result; + } + + /** + * Configures the given map maker to use weak keys, if possible; does nothing + * otherwise (i.e., in GWT). This is sometimes acceptable, when only + * server-side code could generate enough volume that reclamation becomes + * important. + */ + static MapMaker tryWeakKeys(MapMaker mapMaker) { + return mapMaker.weakKeys(); + } + + static SortedMap mapsTransformEntriesSortedMap( + SortedMap fromMap, + EntryTransformer transformer) { + return (fromMap instanceof NavigableMap) + ? Maps.transformEntries((NavigableMap) fromMap, transformer) + : Maps.transformEntriesIgnoreNavigable(fromMap, transformer); + } + + private Platform() {} +} diff --git a/guava/src/com/google/common/collect/Queues.java b/guava/src/com/google/common/collect/Queues.java new file mode 100644 index 0000000..bf1daf2 --- /dev/null +++ b/guava/src/com/google/common/collect/Queues.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +/** + * Static utility methods pertaining to {@link Queue} and {@link Deque} instances. + * Also see this class's counterparts {@link Lists}, {@link Sets}, and {@link Maps}. + * + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@Beta +public final class Queues { + private Queues() {} + + // ArrayBlockingQueue + + /** + * Creates an empty {@code ArrayBlockingQueue} instance. + * + * @return a new, empty {@code ArrayBlockingQueue} + */ + public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { + return new ArrayBlockingQueue(capacity); + } + + // ArrayDeque + + /** + * Creates an empty {@code ArrayDeque} instance. + * + * @return a new, empty {@code ArrayDeque} + * @since 12.0 + */ + public static ArrayDeque newArrayDeque() { + return new ArrayDeque(); + } + + /** + * Creates an {@code ArrayDeque} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code ArrayDeque} containing those elements + * @since 12.0 + */ + public static ArrayDeque newArrayDeque(Iterable elements) { + if (elements instanceof Collection) { + return new ArrayDeque(Collections2.cast(elements)); + } + ArrayDeque deque = new ArrayDeque(); + Iterables.addAll(deque, elements); + return deque; + } + + // ConcurrentLinkedQueue + + /** + * Creates an empty {@code ConcurrentLinkedQueue} instance. + * + * @return a new, empty {@code ConcurrentLinkedQueue} + */ + public static ConcurrentLinkedQueue newConcurrentLinkedQueue() { + return new ConcurrentLinkedQueue(); + } + + /** + * Creates an {@code ConcurrentLinkedQueue} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code ConcurrentLinkedQueue} containing those elements + */ + public static ConcurrentLinkedQueue newConcurrentLinkedQueue( + Iterable elements) { + if (elements instanceof Collection) { + return new ConcurrentLinkedQueue(Collections2.cast(elements)); + } + ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // LinkedBlockingDeque + + /** + * Creates an empty {@code LinkedBlockingDeque} instance. + * + * @return a new, empty {@code LinkedBlockingDeque} + * @since 12.0 + */ + public static LinkedBlockingDeque newLinkedBlockingDeque() { + return new LinkedBlockingDeque(); + } + + /** + * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity. + * + * @param capacity the capacity of this deque + * @return a new, empty {@code LinkedBlockingDeque} + * @throws IllegalArgumentException if {@code capacity} is less than 1 + * @since 12.0 + */ + public static LinkedBlockingDeque newLinkedBlockingDeque(int capacity) { + return new LinkedBlockingDeque(capacity); + } + + /** + * Creates an {@code LinkedBlockingDeque} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code LinkedBlockingDeque} containing those elements + * @since 12.0 + */ + public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable elements) { + if (elements instanceof Collection) { + return new LinkedBlockingDeque(Collections2.cast(elements)); + } + LinkedBlockingDeque deque = new LinkedBlockingDeque(); + Iterables.addAll(deque, elements); + return deque; + } + + // LinkedBlockingQueue + + /** + * Creates an empty {@code LinkedBlockingQueue} instance. + * + * @return a new, empty {@code LinkedBlockingQueue} + */ + public static LinkedBlockingQueue newLinkedBlockingQueue() { + return new LinkedBlockingQueue(); + } + + /** + * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. + * + * @param capacity the capacity of this queue + * @return a new, empty {@code LinkedBlockingQueue} + * @throws IllegalArgumentException if {@code capacity} is less than 1 + */ + public static LinkedBlockingQueue newLinkedBlockingQueue(int capacity) { + return new LinkedBlockingQueue(capacity); + } + + /** + * Creates an {@code LinkedBlockingQueue} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code LinkedBlockingQueue} containing those elements + */ + public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable elements) { + if (elements instanceof Collection) { + return new LinkedBlockingQueue(Collections2.cast(elements)); + } + LinkedBlockingQueue queue = new LinkedBlockingQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // LinkedList: see {@link com.google.common.collect.Lists} + + // PriorityBlockingQueue + + /** + * Creates an empty {@code PriorityBlockingQueue} instance. + * + * @return a new, empty {@code PriorityBlockingQueue} + */ + public static PriorityBlockingQueue newPriorityBlockingQueue() { + return new PriorityBlockingQueue(); + } + + /** + * Creates an {@code PriorityBlockingQueue} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code PriorityBlockingQueue} containing those elements + */ + public static PriorityBlockingQueue newPriorityBlockingQueue( + Iterable elements) { + if (elements instanceof Collection) { + return new PriorityBlockingQueue(Collections2.cast(elements)); + } + PriorityBlockingQueue queue = new PriorityBlockingQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // PriorityQueue + + /** + * Creates an empty {@code PriorityQueue} instance. + * + * @return a new, empty {@code PriorityQueue} + */ + public static PriorityQueue newPriorityQueue() { + return new PriorityQueue(); + } + + /** + * Creates an {@code PriorityQueue} instance containing the given elements. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code PriorityQueue} containing those elements + */ + public static PriorityQueue newPriorityQueue(Iterable elements) { + if (elements instanceof Collection) { + return new PriorityQueue(Collections2.cast(elements)); + } + PriorityQueue queue = new PriorityQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // SynchronousQueue + + /** + * Creates an empty {@code SynchronousQueue} instance. + * + * @return a new, empty {@code SynchronousQueue} + */ + public static SynchronousQueue newSynchronousQueue() { + return new SynchronousQueue(); + } + + /** + * Drains the queue as {@link BlockingQueue#drainTo(Collection, int)}, but if the requested + * {@code numElements} elements are not available, it will wait for them up to the specified + * timeout. + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the timeout parameter + * @return the number of elements transferred + * @throws InterruptedException if interrupted while waiting + */ + public static int drain(BlockingQueue q, Collection buffer, int numElements, + long timeout, TimeUnit unit) throws InterruptedException { + Preconditions.checkNotNull(buffer); + /* + * This code performs one System.nanoTime() more than necessary, and in return, the time to + * execute Queue#drainTo is not added *on top* of waiting for the timeout (which could make + * the timeout arbitrarily inaccurate, given a queue that is slow to drain). + */ + long deadline = System.nanoTime() + unit.toNanos(timeout); + int added = 0; + while (added < numElements) { + // we could rely solely on #poll, but #drainTo might be more efficient when there are multiple + // elements already available (e.g. LinkedBlockingQueue#drainTo locks only once) + added += q.drainTo(buffer, numElements - added); + if (added < numElements) { // not enough elements immediately available; will have to poll + E e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + if (e == null) { + break; // we already waited enough, and there are no more elements in sight + } + buffer.add(e); + added++; + } + } + return added; + } + + /** + * Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, long, TimeUnit)}, + * but with a different behavior in case it is interrupted while waiting. In that case, the + * operation will continue as usual, and in the end the thread's interruption status will be set + * (no {@code InterruptedException} is thrown). + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the timeout parameter + * @return the number of elements transferred + */ + public static int drainUninterruptibly(BlockingQueue q, Collection buffer, + int numElements, long timeout, TimeUnit unit) { + Preconditions.checkNotNull(buffer); + long deadline = System.nanoTime() + unit.toNanos(timeout); + int added = 0; + boolean interrupted = false; + try { + while (added < numElements) { + // we could rely solely on #poll, but #drainTo might be more efficient when there are + // multiple elements already available (e.g. LinkedBlockingQueue#drainTo locks only once) + added += q.drainTo(buffer, numElements - added); + if (added < numElements) { // not enough elements immediately available; will have to poll + E e; // written exactly once, by a successful (uninterrupted) invocation of #poll + while (true) { + try { + e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + break; + } catch (InterruptedException ex) { + interrupted = true; // note interruption and retry + } + } + if (e == null) { + break; // we already waited enough, and there are no more elements in sight + } + buffer.add(e); + added++; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + return added; + } +} diff --git a/guava/src/com/google/common/collect/Range.java b/guava/src/com/google/common/collect/Range.java new file mode 100644 index 0000000..332e4f7 --- /dev/null +++ b/guava/src/com/google/common/collect/Range.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Ranges.create; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Predicate; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A range (or "interval") defines the boundaries around a contiguous span of values of some + * {@code Comparable} type; for example, "integers from 1 to 100 inclusive." Note that it is not + * possible to iterate over these contained values unless an appropriate {@link + * DiscreteDomain} can be provided to the {@link #asSet asSet} method. + * + *

Types of ranges

+ * + *

Each end of the range may be bounded or unbounded. If bounded, there is an associated + * endpoint value, and the range is considered to be either open (does not include the + * endpoint) or closed (includes the endpoint) on that side. With three possibilities on each + * side, this yields nine basic types of ranges, enumerated below. (Notation: a square bracket + * ({@code [ ]}) indicates that the range is closed on that side; a parenthesis ({@code ( )}) means + * it is either open or unbounded. The construct {@code {x | statement}} is read "the set of all + * x such that statement.") + * + *

+ *
Notation Definition Factory method + *
{@code (a..b)} {@code {x | a < x < b}} {@link Ranges#open open} + *
{@code [a..b]} {@code {x | a <= x <= b}}{@link Ranges#closed closed} + *
{@code (a..b]} {@code {x | a < x <= b}} {@link Ranges#openClosed openClosed} + *
{@code [a..b)} {@code {x | a <= x < b}} {@link Ranges#closedOpen closedOpen} + *
{@code (a..+∞)} {@code {x | x > a}} {@link Ranges#greaterThan greaterThan} + *
{@code [a..+∞)} {@code {x | x >= a}} {@link Ranges#atLeast atLeast} + *
{@code (-∞..b)} {@code {x | x < b}} {@link Ranges#lessThan lessThan} + *
{@code (-∞..b]} {@code {x | x <= b}} {@link Ranges#atMost atMost} + *
{@code (-∞..+∞)}{@code {x}} {@link Ranges#all all} + *
+ * + *

When both endpoints exist, the upper endpoint may not be less than the lower. The endpoints + * may be equal only if at least one of the bounds is closed: + * + *

    + *
  • {@code [a..a]} : a singleton range + *
  • {@code [a..a); (a..a]} : {@linkplain #isEmpty empty} ranges; also valid + *
  • {@code (a..a)} : invalid; an exception will be thrown + *
+ * + *

Warnings

+ * + *
    + *
  • Use immutable value types only, if at all possible. If you must use a mutable type, do + * not allow the endpoint instances to mutate after the range is created! + *
  • Your value type's comparison method should be {@linkplain Comparable consistent with equals} + * if at all possible. Otherwise, be aware that concepts used throughout this documentation such + * as "equal", "same", "unique" and so on actually refer to whether {@link Comparable#compareTo + * compareTo} returns zero, not whether {@link Object#equals equals} returns {@code true}. + *
  • A class which implements {@code Comparable} is very broken, and will cause + * undefined horrible things to happen in {@code Range}. For now, the Range API does not prevent + * its use, because this would also rule out all ungenerified (pre-JDK1.5) data types. This + * may change in the future. + *
+ * + *

Other notes

+ * + *
    + *
  • Instances of this type are obtained using the static factory methods in the {@link Ranges} + * class. + *
  • Ranges are convex: whenever two values are contained, all values in between them must + * also be contained. More formally, for any {@code c1 <= c2 <= c3} of type {@code C}, {@code + * r.contains(c1) && r.contains(c3)} implies {@code r.contains(c2)}). This means that a {@code + * Range} can never be used to represent, say, "all prime numbers from 1 to + * 100." + *
  • When evaluated as a {@link Predicate}, a range yields the same result as invoking {@link + * #contains}. + *
  • Terminology note: a range {@code a} is said to be the maximal range having property + * P if, for all ranges {@code b} also having property P, {@code a.encloses(b)}. + * Likewise, {@code a} is minimal when {@code b.encloses(a)} for all {@code b} having + * property P. See, for example, the definition of {@link #intersection intersection}. + *
+ * + *

Further reading

+ * + *

See the Guava User Guide article on + * {@code Range}. + * + * @author Kevin Bourrillion + * @author Gregory Kick + * @since 10.0 + */ +@Beta +@GwtCompatible +@SuppressWarnings("rawtypes") +public final class Range implements Predicate, Serializable { + final Cut lowerBound; + final Cut upperBound; + + Range(Cut lowerBound, Cut upperBound) { + if (lowerBound.compareTo(upperBound) > 0) { + throw new IllegalArgumentException("Invalid range: " + toString(lowerBound, upperBound)); + } + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + /** + * Returns {@code true} if this range has a lower endpoint. + */ + public boolean hasLowerBound() { + return lowerBound != Cut.belowAll(); + } + + /** + * Returns the lower endpoint of this range. + * + * @throws IllegalStateException if this range is unbounded below (that is, {@link + * #hasLowerBound()} returns {@code false}) + */ + public C lowerEndpoint() { + return lowerBound.endpoint(); + } + + /** + * Returns the type of this range's lower bound: {@link BoundType#CLOSED} if the range includes + * its lower endpoint, {@link BoundType#OPEN} if it does not. + * + * @throws IllegalStateException if this range is unbounded below (that is, {@link + * #hasLowerBound()} returns {@code false}) + */ + public BoundType lowerBoundType() { + return lowerBound.typeAsLowerBound(); + } + + /** + * Returns {@code true} if this range has an upper endpoint. + */ + public boolean hasUpperBound() { + return upperBound != Cut.aboveAll(); + } + + /** + * Returns the upper endpoint of this range. + * + * @throws IllegalStateException if this range is unbounded above (that is, {@link + * #hasUpperBound()} returns {@code false}) + */ + public C upperEndpoint() { + return upperBound.endpoint(); + } + + /** + * Returns the type of this range's upper bound: {@link BoundType#CLOSED} if the range includes + * its upper endpoint, {@link BoundType#OPEN} if it does not. + * + * @throws IllegalStateException if this range is unbounded above (that is, {@link + * #hasUpperBound()} returns {@code false}) + */ + public BoundType upperBoundType() { + return upperBound.typeAsUpperBound(); + } + + /** + * Returns {@code true} if this range is of the form {@code [v..v)} or {@code (v..v]}. (This does + * not encompass ranges of the form {@code (v..v)}, because such ranges are invalid and + * can't be constructed at all.) + * + *

Note that certain discrete ranges such as the integer range {@code (3..4)} are not + * considered empty, even though they contain no actual values. + */ + public boolean isEmpty() { + return lowerBound.equals(upperBound); + } + + /** + * Returns {@code true} if {@code value} is within the bounds of this range. For example, on the + * range {@code [0..2)}, {@code contains(1)} returns {@code true}, while {@code contains(2)} + * returns {@code false}. + */ + public boolean contains(C value) { + checkNotNull(value); + // let this throw CCE if there is some trickery going on + return lowerBound.isLessThan(value) && !upperBound.isLessThan(value); + } + + /** + * Equivalent to {@link #contains}; provided only to satisfy the {@link Predicate} interface. When + * using a reference of type {@code Range}, always invoke {@link #contains} directly instead. + */ + @Override public boolean apply(C input) { + return contains(input); + } + + /** + * Returns {@code true} if every element in {@code values} is {@linkplain #contains contained} in + * this range. + */ + public boolean containsAll(Iterable values) { + if (Iterables.isEmpty(values)) { + return true; + } + + // this optimizes testing equality of two range-backed sets + if (values instanceof SortedSet) { + SortedSet set = cast(values); + Comparator comparator = set.comparator(); + if (Ordering.natural().equals(comparator) || comparator == null) { + return contains(set.first()) && contains(set.last()); + } + } + + for (C value : values) { + if (!contains(value)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if the bounds of {@code other} do not extend outside the bounds of this + * range. Examples: + * + *

    + *
  • {@code [3..6]} encloses {@code [4..5]} + *
  • {@code (3..6)} encloses {@code (3..6)} + *
  • {@code [3..6]} encloses {@code [4..4)} (even though the latter is empty) + *
  • {@code (3..6]} does not enclose {@code [3..6]} + *
  • {@code [4..5]} does not enclose {@code (3..6)} (even though it contains every value + * contained by the latter range) + *
  • {@code [3..6]} does not enclose {@code (1..1]} (even though it contains every value + * contained by the latter range) + *
+ * + * Note that if {@code a.encloses(b)}, then {@code b.contains(v)} implies {@code a.contains(v)}, + * but as the last two examples illustrate, the converse is not always true. + * + *

Being reflexive, antisymmetric and transitive, the {@code encloses} relation defines a + * partial order over ranges. There exists a unique {@linkplain Ranges#all maximal} range + * according to this relation, and also numerous {@linkplain #isEmpty minimal} ranges. Enclosure + * also implies {@linkplain #isConnected connectedness}. + */ + public boolean encloses(Range other) { + return lowerBound.compareTo(other.lowerBound) <= 0 + && upperBound.compareTo(other.upperBound) >= 0; + } + + /** + * Returns {@code true} if there exists a (possibly empty) range which is {@linkplain #encloses + * enclosed} by both this range and {@code other}. + * + *

For example, + *

    + *
  • {@code [2, 4)} and {@code [5, 7)} are not connected + *
  • {@code [2, 4)} and {@code [3, 5)} are connected, because both enclose {@code [3, 4)} + *
  • {@code [2, 4)} and {@code [4, 6)} are connected, because both enclose the empty range + * {@code [4, 4)} + *
+ * + *

Note that this range and {@code other} have a well-defined {@linkplain #span union} and + * {@linkplain #intersection intersection} (as a single, possibly-empty range) if and only if this + * method returns {@code true}. + * + *

The connectedness relation is both reflexive and symmetric, but does not form an {@linkplain + * Equivalence equivalence relation} as it is not transitive. + */ + public boolean isConnected(Range other) { + return lowerBound.compareTo(other.upperBound) <= 0 + && other.lowerBound.compareTo(upperBound) <= 0; + } + + /** + * Returns the maximal range {@linkplain #encloses enclosed} by both this range and {@code + * connectedRange}, if such a range exists. + * + *

For example, the intersection of {@code [1..5]} and {@code (3..7)} is {@code (3..5]}. The + * resulting range may be empty; for example, {@code [1..5)} intersected with {@code [5..7)} + * yields the empty range {@code [5..5)}. + * + *

The intersection exists if and only if the two ranges are {@linkplain #isConnected + * connected}. + * + *

The intersection operation is commutative, associative and idempotent, and its identity + * element is {@link Ranges#all}). + * + * @throws IllegalArgumentException if {@code isConnected(connectedRange)} is {@code false} + */ + public Range intersection(Range connectedRange) { + Cut newLower = Ordering.natural().max(lowerBound, connectedRange.lowerBound); + Cut newUpper = Ordering.natural().min(upperBound, connectedRange.upperBound); + return create(newLower, newUpper); + } + + /** + * Returns the minimal range that {@linkplain #encloses encloses} both this range and {@code + * other}. For example, the span of {@code [1..3]} and {@code (5..7)} is {@code [1..7)}. + * + *

If the input ranges are {@linkplain #isConnected connected}, the returned range can + * also be called their union. If they are not, note that the span might contain values + * that are not contained in either input range. + * + *

Like {@link #intersection(Range) intersection}, this operation is commutative, associative + * and idempotent. Unlike it, it is always well-defined for any two input ranges. + */ + public Range span(Range other) { + Cut newLower = Ordering.natural().min(lowerBound, other.lowerBound); + Cut newUpper = Ordering.natural().max(upperBound, other.upperBound); + return create(newLower, newUpper); + } + + /** + * Returns an {@link ContiguousSet} containing the same values in the given domain + * {@linkplain Range#contains contained} by this range. + * + *

Note: {@code a.asSet(d).equals(b.asSet(d))} does not imply {@code a.equals(b)}! For + * example, {@code a} and {@code b} could be {@code [2..4]} and {@code (1..5)}, or the empty + * ranges {@code [3..3)} and {@code [4..4)}. + * + *

Warning: Be extremely careful what you do with the {@code asSet} view of a large + * range (such as {@code Ranges.greaterThan(0)}). Certain operations on such a set can be + * performed efficiently, but others (such as {@link Set#hashCode} or {@link + * Collections#frequency}) can cause major performance problems. + * + *

The returned set's {@link Object#toString} method returns a short-hand form of the set's + * contents, such as {@code "[1..100]}"}. + * + * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if + * neither has an upper bound + */ + // TODO(kevinb): commit in spec to which methods are efficient? + @GwtCompatible(serializable = false) + public ContiguousSet asSet(DiscreteDomain domain) { + return ContiguousSet.create(this, domain); + } + + /** + * Returns the canonical form of this range in the given domain. The canonical form has the + * following properties: + * + *

    + *
  • equivalence: {@code a.canonical().contains(v) == a.contains(v)} for all {@code v} (in other + * words, {@code a.canonical(domain).asSet(domain).equals(a.asSet(domain))} + *
  • uniqueness: unless {@code a.isEmpty()}, {@code a.asSet(domain).equals(b.asSet(domain))} + * implies {@code a.canonical(domain).equals(b.canonical(domain))} + *
  • idempotence: {@code a.canonical(domain).canonical(domain).equals(a.canonical(domain))} + *
+ * + * Furthermore, this method guarantees that the range returned will be one of the following + * canonical forms: + * + *
    + *
  • [start..end) + *
  • [start..+∞) + *
  • (-∞..end) (only if type {@code C} is unbounded below) + *
  • (-∞..+∞) (only if type {@code C} is unbounded below) + *
+ */ + public Range canonical(DiscreteDomain domain) { + checkNotNull(domain); + Cut lower = lowerBound.canonical(domain); + Cut upper = upperBound.canonical(domain); + return (lower == lowerBound && upper == upperBound) ? this : create(lower, upper); + } + + /** + * Returns {@code true} if {@code object} is a range having the same endpoints and bound types as + * this range. Note that discrete ranges such as {@code (1..4)} and {@code [2..3]} are not + * equal to one another, despite the fact that they each contain precisely the same set of values. + * Similarly, empty ranges are not equal unless they have exactly the same representation, so + * {@code [3..3)}, {@code (3..3]}, {@code (4..4]} are all unequal. + */ + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Range) { + Range other = (Range) object; + return lowerBound.equals(other.lowerBound) + && upperBound.equals(other.upperBound); + } + return false; + } + + /** Returns a hash code for this range. */ + @Override public int hashCode() { + return lowerBound.hashCode() * 31 + upperBound.hashCode(); + } + + /** + * Returns a string representation of this range, such as {@code "[3..5)"} (other examples are + * listed in the class documentation). + */ + @Override public String toString() { + return toString(lowerBound, upperBound); + } + + private static String toString(Cut lowerBound, Cut upperBound) { + StringBuilder sb = new StringBuilder(16); + lowerBound.describeAsLowerBound(sb); + sb.append('\u2025'); + upperBound.describeAsUpperBound(sb); + return sb.toString(); + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + private static SortedSet cast(Iterable iterable) { + return (SortedSet) iterable; + } + + @SuppressWarnings("unchecked") // this method may throw CCE + static int compareOrThrow(Comparable left, Comparable right) { + return left.compareTo(right); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/RangeMap.java b/guava/src/com/google/common/collect/RangeMap.java new file mode 100644 index 0000000..0174401 --- /dev/null +++ b/guava/src/com/google/common/collect/RangeMap.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * A mapping from keys to values that efficiently supports mapping entire ranges at once. This + * implementation does not support null values. + * + * @author Louis Wasserman + */ +@GwtIncompatible("NavigableMap") final class RangeMap + implements Function, Serializable { + private final NavigableMap, RangeValue> map; + + /** + * Creates a new, empty {@code RangeMap}. + */ + public static RangeMap create() { + return new RangeMap(new TreeMap, RangeValue>()); + } + + private RangeMap(NavigableMap, RangeValue> map) { + this.map = map; + } + + /** + * Equivalent to {@link #get(Comparable) get(K)}, provided only to satisfy the {@link Function} + * interface. When using a reference of type {@code RangeMap}, always invoke + * {@link #get(Comparable) get(K)} directly instead. + */ + @Override + public V apply(K input) { + return get(input); + } + + /** + * Returns the value associated with {@code key}, or {@code null} if there is no such value. + */ + @Nullable + public V get(K key) { + Entry, RangeValue> lowerEntry = map.lowerEntry(Cut.aboveValue(key)); + if (lowerEntry != null && lowerEntry.getValue().getKey().contains(key)) { + return lowerEntry.getValue().getValue(); + } + return null; + } + + /** + * Associates {@code value} with every key {@linkplain Range#contains contained} in {@code + * keyRange}. + * + *

This method takes amortized O(log n) time. + */ + public void put(Range keyRange, V value) { + checkNotNull(keyRange); + checkNotNull(value); + if (keyRange.isEmpty()) { + return; + } + clear(keyRange); + putRange(new RangeValue(keyRange, value)); + } + + /** + * Puts all the associations from the specified {@code RangeMap} into this {@code RangeMap}. + */ + public void putAll(RangeMap rangeMap) { + checkNotNull(rangeMap); + for (RangeValue rangeValue : rangeMap.map.values()) { + put(rangeValue.getKey(), rangeValue.getValue()); + } + } + + /** + * Clears all associations from this {@code RangeMap}. + */ + public void clear() { + map.clear(); + } + + /** + * Removes all associations to keys {@linkplain Range#contains contained} in {@code + * rangeToClear}. + */ + public void clear(Range rangeToClear) { + checkNotNull(rangeToClear); + if (rangeToClear.isEmpty()) { + return; + } + + Entry, RangeValue> lowerThanLB = map.lowerEntry(rangeToClear.lowerBound); + // We will use { } to denote the ends of rangeToClear, and < > to denote the ends of + // other ranges currently in the map. For example, < { > indicates that we know that + // rangeToClear.lowerBound is between the bounds of some range already in the map. + + if (lowerThanLB != null) { + RangeValue lowerRangeValue = lowerThanLB.getValue(); + Cut upperCut = lowerRangeValue.getUpperBound(); + if (upperCut.compareTo(rangeToClear.lowerBound) >= 0) { // < { > + RangeValue replacement = lowerRangeValue.withUpperBound(rangeToClear.lowerBound); + if (replacement == null) { + removeRange(lowerRangeValue); + } else { + putRange(replacement); // overwrites old range + } + if (upperCut.compareTo(rangeToClear.upperBound) >= 0) { // < { } > + putRange(lowerRangeValue.withLowerBound(rangeToClear.upperBound)); + return; // we must be done + } + } + } + + Entry, RangeValue> lowerThanUB = map.lowerEntry(rangeToClear.upperBound); + if (lowerThanUB != null) { + RangeValue lowerRangeValue = lowerThanUB.getValue(); + Cut upperCut = lowerRangeValue.getUpperBound(); + if (upperCut.compareTo(rangeToClear.upperBound) >= 0) { // < } > + // we can't have < { } >, we already dealt with that + removeRange(lowerRangeValue); + putRange(lowerRangeValue.withLowerBound(rangeToClear.upperBound)); + } + } + + // everything left with { < } is a { < > }, so we clear it indiscriminately + map.subMap(rangeToClear.lowerBound, rangeToClear.upperBound).clear(); + } + + private void removeRange(RangeValue rangeValue) { + RangeValue removed = map.remove(rangeValue.getLowerBound()); + assert removed == rangeValue; + } + + private void putRange(@Nullable RangeValue rangeValue) { + if (rangeValue != null && !rangeValue.getKey().isEmpty()) { + map.put(rangeValue.getLowerBound(), rangeValue); + } + } + + private static final class RangeValue extends AbstractMap.SimpleEntry< + Range, V> { + + RangeValue(Range key, V value) { + super(checkNotNull(key), checkNotNull(value)); + assert !key.isEmpty(); + } + + Cut getLowerBound() { + return getKey().lowerBound; + } + + Cut getUpperBound() { + return getKey().upperBound; + } + + @Nullable + RangeValue withLowerBound(Cut newLowerBound) { + Range newRange = new Range(newLowerBound, getUpperBound()); + return newRange.isEmpty() ? null : new RangeValue(newRange, getValue()); + } + + @Nullable + RangeValue withUpperBound(Cut newUpperBound) { + Range newRange = new Range(getLowerBound(), newUpperBound); + return newRange.isEmpty() ? null : new RangeValue(newRange, getValue()); + } + + private static final long serialVersionUID = 0L; + } + + /** + * Compares the specified object with this {@code RangeMap} for equality. It is guaranteed that: + *

    + *
  • The relation defined by this method is reflexive, symmetric, and transitive, as required + * by the contract of {@link Object#equals(Object)}. + *
  • Two empty range maps are always equal. + *
  • If two range maps are equal, and the same operation is performed on each, the resulting + * range maps are also equal. + *
  • If {@code rangeMap1.equals(rangeMap2)}, it is guaranteed that {@code rangeMap1.get(k)} + * is equal to {@code rangeMap2.get(k)}. + *
+ */ + @Override + public boolean equals(@Nullable Object o) { + return o instanceof RangeMap && map.equals(((RangeMap) o).map); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return map.toString(); + } + + private static final long serialVersionUID = 0L; +} diff --git a/guava/src/com/google/common/collect/RangeSet.java b/guava/src/com/google/common/collect/RangeSet.java new file mode 100644 index 0000000..d393991 --- /dev/null +++ b/guava/src/com/google/common/collect/RangeSet.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A set of values of type {@code C} made up of zero or more disjoint {@linkplain Range + * ranges}. + * + *

It is guaranteed that {@linkplain Range#isConnected connected} ranges will be + * {@linkplain Range#span coalesced} together, and that {@linkplain Range#isEmpty empty} ranges + * will never be held in a {@code RangeSet}. + * + *

For a {@link Set} whose contents are specified by a {@link Range}, see {@link ContiguousSet}. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + */ abstract class RangeSet { + RangeSet() {} + + /** + * Determines whether any of this range set's member ranges contains {@code value}. + */ + public boolean contains(C value) { + return rangeContaining(value) != null; + } + + /** + * Returns the unique range from this range set that {@linkplain Range#contains contains} + * {@code value}, or {@code null} if this range set does not contain {@code value}. + */ + public Range rangeContaining(C value) { + checkNotNull(value); + for (Range range : asRanges()) { + if (range.contains(value)) { + return range; + } + } + return null; + } + + /** + * Returns a view of the {@linkplain Range#isConnected disconnected} ranges that make up this + * range set. The returned set may be empty. The iterators returned by its + * {@link Iterable#iterator} method return the ranges in increasing order of lower bound + * (equivalently, of upper bound). + */ + public abstract Set> asRanges(); + + /** + * Returns {@code true} if this range set contains no ranges. + */ + public boolean isEmpty() { + return asRanges().isEmpty(); + } + + /** + * Returns a view of the complement of this {@code RangeSet}. + * + *

The returned view supports the {@link #add} operation if this {@code RangeSet} supports + * {@link #remove}, and vice versa. + */ + public abstract RangeSet complement(); + + /** + * A basic, simple implementation of {@link #complement}. This is not efficient on all methods; + * for example, {@link #rangeContaining} and {@link #encloses} are linear-time. + */ + static class StandardComplement extends RangeSet { + final RangeSet positive; + + public StandardComplement(RangeSet positive) { + this.positive = positive; + } + + @Override + public boolean contains(C value) { + return !positive.contains(value); + } + + @Override + public void add(Range range) { + positive.remove(range); + } + + @Override + public void remove(Range range) { + positive.add(range); + } + + private transient Set> asRanges; + + @Override + public final Set> asRanges() { + Set> result = asRanges; + return (result == null) ? asRanges = createAsRanges() : result; + } + + Set> createAsRanges() { + return new AbstractSet>() { + + @Override + public Iterator> iterator() { + final Iterator> positiveIterator = positive.asRanges().iterator(); + return new AbstractIterator>() { + Cut prevCut = Cut.belowAll(); + + @Override + protected Range computeNext() { + while (positiveIterator.hasNext()) { + Cut oldCut = prevCut; + Range positiveRange = positiveIterator.next(); + prevCut = positiveRange.upperBound; + if (oldCut.compareTo(positiveRange.lowerBound) < 0) { + return new Range(oldCut, positiveRange.lowerBound); + } + } + Cut posInfinity = Cut.aboveAll(); + if (prevCut.compareTo(posInfinity) < 0) { + Range result = new Range(prevCut, posInfinity); + prevCut = posInfinity; + return result; + } + return endOfData(); + } + }; + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + }; + } + + @Override + public RangeSet complement() { + return positive; + } + } + + /** + * Adds the specified range to this {@code RangeSet} (optional operation). That is, for equal + * range sets a and b, the result of {@code a.add(range)} is that {@code a} will be the minimal + * range set for which both {@code a.enclosesAll(b)} and {@code a.encloses(range)}. + * + *

Note that {@code range} will be {@linkplain Range#span(Range) coalesced} with any ranges in + * the range set that are {@linkplain Range#isConnected(Range) connected} with it. Moreover, + * if {@code range} is empty, this is a no-op. + * + * @throws UnsupportedOperationException if this range set does not support the {@code add} + * operation + */ + public void add(Range range) { + throw new UnsupportedOperationException(); + } + + /** + * Removes the specified range from this {@code RangeSet} (optional operation). After this + * operation, if {@code range.contains(c)}, {@code this.contains(c)} will return {@code false}. + * + *

If {@code range} is empty, this is a no-op. + * + * @throws UnsupportedOperationException if this range set does not support the {@code remove} + * operation + */ + public void remove(Range range) { + throw new UnsupportedOperationException(); + } + + /** + * Returns {@code true} if there exists a member range in this range set which + * {@linkplain Range#encloses encloses} the specified range. + */ + public boolean encloses(Range otherRange) { + for (Range range : asRanges()) { + if (range.encloses(otherRange)) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if for each member range in {@code other} there exists a member range in + * this range set which {@linkplain Range#encloses encloses} it. It follows that + * {@code this.contains(value)} whenever {@code other.contains(value)}. Returns {@code true} if + * {@code other} is empty. + * + *

+ * This is equivalent to checking if this range set {@link #encloses} each of the ranges in + * {@code other}. + */ + public boolean enclosesAll(RangeSet other) { + for (Range range : other.asRanges()) { + if (!encloses(range)) { + return false; + } + } + return true; + } + + /** + * Adds all of the ranges from the specified range set to this range set (optional operation). + * After this operation, this range set is the minimal range set that + * {@linkplain #enclosesAll(RangeSet) encloses} both the original range set and {@code other}. + * + *

+ * This is equivalent to calling {@link #add} on each of the ranges in {@code other} in turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code addAll} + * operation + */ + public void addAll(RangeSet other) { + for (Range range : other.asRanges()) { + this.add(range); + } + } + + /** + * Removes all of the ranges from the specified range set from this range set (optional + * operation). After this operation, if {@code other.contains(c)}, {@code this.contains(c)} will + * return {@code false}. + * + *

+ * This is equivalent to calling {@link #remove} on each of the ranges in {@code other} in turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code removeAll} + * operation + */ + public void removeAll(RangeSet other) { + for (Range range : other.asRanges()) { + this.remove(range); + } + } + + /** + * Returns {@code true} if {@code obj} is another {@code RangeSet} that contains the same ranges + * according to {@link Range#equals(Object)}. + */ + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof RangeSet) { + RangeSet other = (RangeSet) obj; + return this.asRanges().equals(other.asRanges()); + } + return false; + } + + @Override + public final int hashCode() { + return asRanges().hashCode(); + } + + /** + * Returns a readable string representation of this range set. For example, if this + * {@code RangeSet} consisted of {@code Ranges.closed(1, 3)} and {@code Ranges.greaterThan(4)}, + * this might return {@code " [1‥3](4‥+∞)}"}. + */ + @Override + public final String toString() { + StringBuilder builder = new StringBuilder(); + builder.append('{'); + for (Range range : asRanges()) { + builder.append(range); + } + builder.append('}'); + return builder.toString(); + } +} diff --git a/guava/src/com/google/common/collect/Ranges.java b/guava/src/com/google/common/collect/Ranges.java new file mode 100644 index 0000000..4062146 --- /dev/null +++ b/guava/src/com/google/common/collect/Ranges.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Static methods pertaining to {@link Range} instances. Each of the + * {@link Range nine types of ranges} can be constructed with a corresponding + * factory method: + * + *

+ *
{@code (a..b)} + *
{@link #open} + *
{@code [a..b]} + *
{@link #closed} + *
{@code [a..b)} + *
{@link #closedOpen} + *
{@code (a..b]} + *
{@link #openClosed} + *
{@code (a..+∞)} + *
{@link #greaterThan} + *
{@code [a..+∞)} + *
{@link #atLeast} + *
{@code (-∞..b)} + *
{@link #lessThan} + *
{@code (-∞..b]} + *
{@link #atMost} + *
{@code (-∞..+∞)} + *
{@link #all} + *
+ * + *

Additionally, {@link Range} instances can be constructed by passing the + * {@link BoundType bound types} explicitly. + * + *

+ *
Bounded on both ends + *
{@link #range} + *
Unbounded on top ({@code (a..+∞)} or {@code (a..+∞)}) + *
{@link #downTo} + *
Unbounded on bottom ({@code (-∞..b)} or {@code (-∞..b]}) + *
{@link #upTo} + *
+ * + *

See the Guava User Guide article on + * {@code Range}. + * + * @author Kevin Bourrillion + * @author Gregory Kick + * @since 10.0 + */ +@GwtCompatible +@Beta +public final class Ranges { + private Ranges() {} + + static > Range create( + Cut lowerBound, Cut upperBound) { + return new Range(lowerBound, upperBound); + } + + /** + * Returns a range that contains all values strictly greater than {@code + * lower} and strictly less than {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than or + * equal to {@code upper} + */ + public static > Range open(C lower, C upper) { + return create(Cut.aboveValue(lower), Cut.belowValue(upper)); + } + + /** + * Returns a range that contains all values greater than or equal to + * {@code lower} and less than or equal to {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code + * upper} + */ + public static > Range closed(C lower, C upper) { + return create(Cut.belowValue(lower), Cut.aboveValue(upper)); + } + + /** + * Returns a range that contains all values greater than or equal to + * {@code lower} and strictly less than {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code + * upper} + */ + public static > Range closedOpen( + C lower, C upper) { + return create(Cut.belowValue(lower), Cut.belowValue(upper)); + } + + /** + * Returns a range that contains all values strictly greater than {@code + * lower} and less than or equal to {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code + * upper} + */ + public static > Range openClosed( + C lower, C upper) { + return create(Cut.aboveValue(lower), Cut.aboveValue(upper)); + } + + /** + * Returns a range that contains any value from {@code lower} to {@code + * upper}, where each endpoint may be either inclusive (closed) or exclusive + * (open). + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code + * upper} + */ + public static > Range range( + C lower, BoundType lowerType, C upper, BoundType upperType) { + checkNotNull(lowerType); + checkNotNull(upperType); + + Cut lowerBound = (lowerType == BoundType.OPEN) + ? Cut.aboveValue(lower) + : Cut.belowValue(lower); + Cut upperBound = (upperType == BoundType.OPEN) + ? Cut.belowValue(upper) + : Cut.aboveValue(upper); + return create(lowerBound, upperBound); + } + + /** + * Returns a range that contains all values strictly less than {@code + * endpoint}. + */ + public static > Range lessThan(C endpoint) { + return create(Cut.belowAll(), Cut.belowValue(endpoint)); + } + + /** + * Returns a range that contains all values less than or equal to + * {@code endpoint}. + */ + public static > Range atMost(C endpoint) { + return create(Cut.belowAll(), Cut.aboveValue(endpoint)); + } + + /** + * Returns a range with no lower bound up to the given endpoint, which may be + * either inclusive (closed) or exclusive (open). + */ + public static > Range upTo( + C endpoint, BoundType boundType) { + switch (boundType) { + case OPEN: + return lessThan(endpoint); + case CLOSED: + return atMost(endpoint); + default: + throw new AssertionError(); + } + } + + /** + * Returns a range that contains all values strictly greater than {@code + * endpoint}. + */ + public static > Range greaterThan(C endpoint) { + return create(Cut.aboveValue(endpoint), Cut.aboveAll()); + } + + /** + * Returns a range that contains all values greater than or equal to + * {@code endpoint}. + */ + public static > Range atLeast(C endpoint) { + return create(Cut.belowValue(endpoint), Cut.aboveAll()); + } + + /** + * Returns a range from the given endpoint, which may be either inclusive + * (closed) or exclusive (open), with no upper bound. + */ + public static > Range downTo( + C endpoint, BoundType boundType) { + switch (boundType) { + case OPEN: + return greaterThan(endpoint); + case CLOSED: + return atLeast(endpoint); + default: + throw new AssertionError(); + } + } + + /** Returns a range that contains every value of type {@code C}. */ + public static > Range all() { + return create(Cut.belowAll(), Cut.aboveAll()); + } + + /** + * Returns a range that {@linkplain Range#contains(Comparable) contains} only + * the given value. The returned range is {@linkplain BoundType#CLOSED closed} + * on both ends. + */ + public static > Range singleton(C value) { + return closed(value, value); + } + + /** + * Returns the minimal range that + * {@linkplain Range#contains(Comparable) contains} all of the given values. + * The returned range is {@linkplain BoundType#CLOSED closed} on both ends. + * + * @throws ClassCastException if the parameters are not mutually + * comparable + * @throws NoSuchElementException if {@code values} is empty + * @throws NullPointerException if any of {@code values} is null + */ + public static > Range encloseAll( + Iterable values) { + checkNotNull(values); + if (values instanceof ContiguousSet) { + return ((ContiguousSet) values).range(); + } + Iterator valueIterator = values.iterator(); + C min = checkNotNull(valueIterator.next()); + C max = min; + while (valueIterator.hasNext()) { + C value = checkNotNull(valueIterator.next()); + min = Ordering.natural().min(min, value); + max = Ordering.natural().max(max, value); + } + return closed(min, max); + } +} diff --git a/guava/src/com/google/common/collect/RegularContiguousSet.java b/guava/src/com/google/common/collect/RegularContiguousSet.java new file mode 100644 index 0000000..65d35c1 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularContiguousSet.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.BoundType.CLOSED; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.Collection; + +import javax.annotation.Nullable; + +/** + * An implementation of {@link ContiguousSet} that contains one or more elements. + * + * @author Gregory Kick + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("unchecked") // allow ungenerified Comparable types +final class RegularContiguousSet extends ContiguousSet { + private final Range range; + + RegularContiguousSet(Range range, DiscreteDomain domain) { + super(domain); + this.range = range; + } + + private ContiguousSet intersectionInCurrentDomain(Range other) { + return (range.isConnected(other)) + ? range.intersection(other).asSet(domain) + : new EmptyContiguousSet(domain); + } + + @Override ContiguousSet headSetImpl(C toElement, boolean inclusive) { + return intersectionInCurrentDomain(Ranges.upTo(toElement, BoundType.forBoolean(inclusive))); + } + + @Override ContiguousSet subSetImpl(C fromElement, boolean fromInclusive, C toElement, + boolean toInclusive) { + if (fromElement.compareTo(toElement) == 0 && !fromInclusive && !toInclusive) { + // Range would reject our attempt to create (x, x). + return new EmptyContiguousSet(domain); + } + return intersectionInCurrentDomain(Ranges.range( + fromElement, BoundType.forBoolean(fromInclusive), + toElement, BoundType.forBoolean(toInclusive))); + } + + @Override ContiguousSet tailSetImpl(C fromElement, boolean inclusive) { + return intersectionInCurrentDomain(Ranges.downTo(fromElement, BoundType.forBoolean(inclusive))); + } + + @GwtIncompatible("not used by GWT emulation") + @Override int indexOf(Object target) { + return contains(target) ? (int) domain.distance(first(), (C) target) : -1; + } + + @Override public UnmodifiableIterator iterator() { + return new AbstractSequentialIterator(first()) { + final C last = last(); + + @Override + protected C computeNext(C previous) { + return equalsOrThrow(previous, last) ? null : domain.next(previous); + } + }; + } + + private static boolean equalsOrThrow(Comparable left, @Nullable Comparable right) { + return right != null && Range.compareOrThrow(left, right) == 0; + } + + @Override boolean isPartialView() { + return false; + } + + @Override public C first() { + return range.lowerBound.leastValueAbove(domain); + } + + @Override public C last() { + return range.upperBound.greatestValueBelow(domain); + } + + @Override public int size() { + long distance = domain.distance(first(), last()); + return (distance >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) distance + 1; + } + + @Override public boolean contains(Object object) { + if (object == null) { + return false; + } + try { + return range.contains((C) object); + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean containsAll(Collection targets) { + return Collections2.containsAllImpl(this, targets); + } + + @Override public boolean isEmpty() { + return false; + } + + // copied to make sure not to use the GWT-emulated version + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + // copied to make sure not to use the GWT-emulated version + @Override public T[] toArray(T[] other) { + return ObjectArrays.toArrayImpl(this, other); + } + + @Override public ContiguousSet intersection(ContiguousSet other) { + checkNotNull(other); + checkArgument(this.domain.equals(other.domain)); + if (other.isEmpty()) { + return other; + } else { + C lowerEndpoint = Ordering.natural().max(this.first(), other.first()); + C upperEndpoint = Ordering.natural().min(this.last(), other.last()); + return (lowerEndpoint.compareTo(upperEndpoint) < 0) + ? Ranges.closed(lowerEndpoint, upperEndpoint).asSet(domain) + : new EmptyContiguousSet(domain); + } + } + + @Override public Range range() { + return range(CLOSED, CLOSED); + } + + @Override public Range range(BoundType lowerBoundType, BoundType upperBoundType) { + return Ranges.create(range.lowerBound.withLowerBoundType(lowerBoundType, domain), + range.upperBound.withUpperBoundType(upperBoundType, domain)); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object instanceof RegularContiguousSet) { + RegularContiguousSet that = (RegularContiguousSet) object; + if (this.domain.equals(that.domain)) { + return this.first().equals(that.first()) + && this.last().equals(that.last()); + } + } + return super.equals(object); + } + + // copied to make sure not to use the GWT-emulated version + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @GwtIncompatible("serialization") + private static final class SerializedForm implements Serializable { + final Range range; + final DiscreteDomain domain; + + private SerializedForm(Range range, DiscreteDomain domain) { + this.range = range; + this.domain = domain; + } + + private Object readResolve() { + return new RegularContiguousSet(range, domain); + } + } + + @GwtIncompatible("serialization") + @Override Object writeReplace() { + return new SerializedForm(range, domain); + } + + private static final long serialVersionUID = 0; + + @GwtIncompatible("NavigableSet") + @Override + ImmutableSortedSet createDescendingSet() { + return new DescendingContiguousSet(); + } + + @GwtIncompatible("NavigableSet") + private final class DescendingContiguousSet extends ImmutableSortedSet { + + private DescendingContiguousSet() { + super(Ordering.natural().reverse()); + } + + @Override + public C first() { + return RegularContiguousSet.this.last(); + } + + @Override + public C last() { + return RegularContiguousSet.this.first(); + } + + @Override + public int size() { + return RegularContiguousSet.this.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return new AbstractSequentialIterator(first()) { + final C last = last(); + + @Override + protected C computeNext(C previous) { + return equalsOrThrow(previous, last) ? null : domain.previous(previous); + } + }; + } + + @Override + ImmutableSortedSet headSetImpl(C toElement, boolean inclusive) { + return RegularContiguousSet.this.tailSetImpl(toElement, inclusive).descendingSet(); + } + + @Override + ImmutableSortedSet subSetImpl( + C fromElement, + boolean fromInclusive, + C toElement, + boolean toInclusive) { + return RegularContiguousSet.this.subSetImpl( + toElement, + toInclusive, + fromElement, + fromInclusive).descendingSet(); + } + + @Override + ImmutableSortedSet tailSetImpl(C fromElement, boolean inclusive) { + return RegularContiguousSet.this.headSetImpl(fromElement, inclusive).descendingSet(); + } + + @Override + ImmutableSortedSet createDescendingSet() { + return RegularContiguousSet.this; + } + + @Override + int indexOf(Object target) { + return contains(target) ? (int) domain.distance(last(), (C) target) : -1; + } + + @Override + boolean isPartialView() { + return false; + } + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableAsList.java b/guava/src/com/google/common/collect/RegularImmutableAsList.java new file mode 100644 index 0000000..c137d6b --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableAsList.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * An {@link ImmutableAsList} implementation specialized for when the delegate collection is + * already backed by an {@code ImmutableList} or array. + * + * @author Louis Wasserman + */ +@GwtCompatible +@SuppressWarnings("serial") // uses writeReplace, not default serialization +class RegularImmutableAsList extends ImmutableAsList { + private final ImmutableCollection delegate; + private final ImmutableList delegateList; + + RegularImmutableAsList(ImmutableCollection delegate, ImmutableList delegateList) { + this.delegate = delegate; + this.delegateList = delegateList; + } + + RegularImmutableAsList(ImmutableCollection delegate, Object[] array) { + this(delegate, ImmutableList.asImmutableList(array)); + } + + @Override + ImmutableCollection delegateCollection() { + return delegate; + } + + ImmutableList delegateList() { + return delegateList; + } + + @SuppressWarnings("unchecked") // safe covariant cast! + @Override + public UnmodifiableListIterator listIterator(int index) { + return (UnmodifiableListIterator) delegateList.listIterator(index); + } + + @Override + public Object[] toArray() { + return delegateList.toArray(); + } + + @Override + public T[] toArray(T[] other) { + return delegateList.toArray(other); + } + + @Override + public int indexOf(Object object) { + return delegateList.indexOf(object); + } + + @Override + public int lastIndexOf(Object object) { + return delegateList.lastIndexOf(object); + } + + @Override + public boolean equals(Object obj) { + return delegateList.equals(obj); + } + + @Override + public int hashCode() { + return delegateList.hashCode(); + } + + @Override + public E get(int index) { + return delegateList.get(index); + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableBiMap.java b/guava/src/com/google/common/collect/RegularImmutableBiMap.java new file mode 100644 index 0000000..dca1f05 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableBiMap.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Bimap with one or more mappings. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableBiMap extends ImmutableBiMap { + final transient ImmutableMap delegate; + final transient ImmutableBiMap inverse; + + RegularImmutableBiMap(ImmutableMap delegate) { + this.delegate = delegate; + + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Entry entry : delegate.entrySet()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableMap backwardMap = builder.build(); + this.inverse = new RegularImmutableBiMap(backwardMap, this); + } + + RegularImmutableBiMap(ImmutableMap delegate, + ImmutableBiMap inverse) { + this.delegate = delegate; + this.inverse = inverse; + } + + @Override ImmutableMap delegate() { + return delegate; + } + + @Override public ImmutableBiMap inverse() { + return inverse; + } + + @Override boolean isPartialView() { + return delegate.isPartialView() || inverse.delegate().isPartialView(); + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableList.java b/guava/src/com/google/common/collect/RegularImmutableList.java new file mode 100644 index 0000000..4314b6e --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableList.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableList} with one or more elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableList extends ImmutableList { + private final transient int offset; + private final transient int size; + private final transient Object[] array; + + RegularImmutableList(Object[] array, int offset, int size) { + this.offset = offset; + this.size = size; + this.array = array; + } + + RegularImmutableList(Object[] array) { + this(array, 0, array.length); + } + + @Override + public int size() { + return size; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override boolean isPartialView() { + return offset != 0 || size != array.length; + } + + @Override public Object[] toArray() { + Object[] newArray = new Object[size()]; + System.arraycopy(array, offset, newArray, 0, size); + return newArray; + } + + @Override public T[] toArray(T[] other) { + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + System.arraycopy(array, offset, other, 0, size); + return other; + } + + // The fake cast to E is safe because the creation methods only allow E's + @Override + @SuppressWarnings("unchecked") + public E get(int index) { + Preconditions.checkElementIndex(index, size); + return (E) array[index + offset]; + } + + @Override + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + return new RegularImmutableList( + array, offset + fromIndex, toIndex - fromIndex); + } + + @SuppressWarnings("unchecked") + @Override + public UnmodifiableListIterator listIterator(int index) { + // for performance + // The fake cast to E is safe because the creation methods only allow E's + return (UnmodifiableListIterator) + Iterators.forArray(array, offset, size, index); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof List)) { + return false; + } + + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + + int index = offset; + if (object instanceof RegularImmutableList) { + RegularImmutableList other = (RegularImmutableList) object; + for (int i = other.offset; i < other.offset + other.size; i++) { + if (!array[index++].equals(other.array[i])) { + return false; + } + } + } else { + for (Object element : that) { + if (!array[index++].equals(element)) { + return false; + } + } + } + return true; + } + + @Override public String toString() { + StringBuilder sb = Collections2.newStringBuilderForCollection(size()) + .append('[').append(array[offset]); + for (int i = offset + 1; i < offset + size; i++) { + sb.append(", ").append(array[i]); + } + return sb.append(']').toString(); + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableMap.java b/guava/src/com/google/common/collect/RegularImmutableMap.java new file mode 100644 index 0000000..7a2e859 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableMap.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Implementation of {@link ImmutableMap} with two or more entries. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @author Gregory Kick + */ +@GwtCompatible(serializable = true, emulated = true) +final class RegularImmutableMap extends ImmutableMap { + + // entries in insertion order + private final transient LinkedEntry[] entries; + // array of linked lists of entries + private final transient LinkedEntry[] table; + // 'and' with an int to get a table index + private final transient int mask; + private final transient int keySetHashCode; + + // TODO(gak): investigate avoiding the creation of ImmutableEntries since we + // re-copy them anyway. + RegularImmutableMap(Entry... immutableEntries) { + int size = immutableEntries.length; + entries = createEntryArray(size); + + int tableSize = chooseTableSize(size); + table = createEntryArray(tableSize); + mask = tableSize - 1; + + int keySetHashCodeMutable = 0; + for (int entryIndex = 0; entryIndex < size; entryIndex++) { + // each of our 6 callers carefully put only Entrys into the array! + @SuppressWarnings("unchecked") + Entry entry = (Entry) immutableEntries[entryIndex]; + K key = entry.getKey(); + int keyHashCode = key.hashCode(); + keySetHashCodeMutable += keyHashCode; + int tableIndex = Hashing.smear(keyHashCode) & mask; + @Nullable LinkedEntry existing = table[tableIndex]; + // prepend, not append, so the entries can be immutable + LinkedEntry linkedEntry = + newLinkedEntry(key, entry.getValue(), existing); + table[tableIndex] = linkedEntry; + entries[entryIndex] = linkedEntry; + while (existing != null) { + checkArgument(!key.equals(existing.getKey()), "duplicate key: %s", key); + existing = existing.next(); + } + } + keySetHashCode = keySetHashCodeMutable; + } + + /** + * Closed addressing tends to perform well even with high load factors. + * Being conservative here ensures that the table is still likely to be + * relatively sparse (hence it misses fast) while saving space. + */ + private static final double MAX_LOAD_FACTOR = 1.2; + + /** + * Give a good hash table size for the given number of keys. + * + * @param size The number of keys to be inserted. Must be greater than or equal to 2. + */ + private static int chooseTableSize(int size) { + // Get the recommended table size. + // Round down to the nearest power of 2. + int tableSize = Integer.highestOneBit(size); + // Check to make sure that we will not exceed the maximum load factor. + if ((double) size / tableSize > MAX_LOAD_FACTOR) { + tableSize <<= 1; + checkArgument(tableSize > 0, "table too large: %s", size); + } + return tableSize; + } + + /** + * Creates a {@link LinkedEntry} array to hold parameterized entries. The + * result must never be upcast back to LinkedEntry[] (or Object[], etc.), or + * allowed to escape the class. + */ + @SuppressWarnings("unchecked") // Safe as long as the javadocs are followed + private LinkedEntry[] createEntryArray(int size) { + return new LinkedEntry[size]; + } + + private static LinkedEntry newLinkedEntry(K key, V value, + @Nullable LinkedEntry next) { + return (next == null) + ? new TerminalEntry(key, value) + : new NonTerminalEntry(key, value, next); + } + + private interface LinkedEntry extends Entry { + /** Returns the next entry in the list or {@code null} if none exists. */ + @Nullable LinkedEntry next(); + } + + /** {@code LinkedEntry} implementation that has a next value. */ + @Immutable + @SuppressWarnings("serial") // this class is never serialized + private static final class NonTerminalEntry + extends ImmutableEntry implements LinkedEntry { + final LinkedEntry next; + + NonTerminalEntry(K key, V value, LinkedEntry next) { + super(key, value); + this.next = next; + } + + @Override public LinkedEntry next() { + return next; + } + } + + /** + * {@code LinkedEntry} implementation that serves as the last entry in the + * list. I.e. no next entry + */ + @Immutable + @SuppressWarnings("serial") // this class is never serialized + private static final class TerminalEntry extends ImmutableEntry + implements LinkedEntry { + TerminalEntry(K key, V value) { + super(key, value); + } + + @Nullable @Override public LinkedEntry next() { + return null; + } + } + + @Override public V get(@Nullable Object key) { + if (key == null) { + return null; + } + int index = Hashing.smear(key.hashCode()) & mask; + for (LinkedEntry entry = table[index]; + entry != null; + entry = entry.next()) { + K candidateKey = entry.getKey(); + + /* + * Assume that equals uses the == optimization when appropriate, and that + * it would check hash codes as an optimization when appropriate. If we + * did these things, it would just make things worse for the most + * performance-conscious users. + */ + if (key.equals(candidateKey)) { + return entry.getValue(); + } + } + return null; + } + + @Override + public int size() { + return entries.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean containsValue(@Nullable Object value) { + if (value == null) { + return false; + } + for (Entry entry : entries) { + if (entry.getValue().equals(value)) { + return true; + } + } + return false; + } + + @Override boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return new EntrySet(); + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private class EntrySet extends ImmutableMapEntrySet { + @Override ImmutableMap map() { + return RegularImmutableMap.this; + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + return new RegularImmutableAsList>(this, entries); + } + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet(entrySet(), keySetHashCode) { + @Override ImmutableMap map() { + return RegularImmutableMap.this; + } + }; + } + + @Override public String toString() { + StringBuilder result + = Collections2.newStringBuilderForCollection(size()).append('{'); + Collections2.STANDARD_JOINER.appendTo(result, entries); + return result.append('}').toString(); + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/RegularImmutableMultiset.java b/guava/src/com/google/common/collect/RegularImmutableMultiset.java new file mode 100644 index 0000000..e46f424 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableMultiset.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableMultiset} with one or more elements. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") +// uses writeReplace(), not default serialization +class RegularImmutableMultiset extends ImmutableMultiset { + private final transient ImmutableMap map; + private final transient int size; + + RegularImmutableMultiset(ImmutableMap map, int size) { + this.map = map; + this.size = size; + } + + @Override + boolean isPartialView() { + return map.isPartialView(); + } + + @Override + public int count(@Nullable Object element) { + Integer value = map.get(element); + return (value == null) ? 0 : value; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(@Nullable Object element) { + return map.containsKey(element); + } + + @Override + public ImmutableSet elementSet() { + return map.keySet(); + } + + private static Entry entryFromMapEntry(Map.Entry entry) { + return Multisets.immutableEntry(entry.getKey(), entry.getValue()); + } + + @Override + ImmutableSet> createEntrySet() { + return new EntrySet(); + } + + private class EntrySet extends ImmutableMultiset.EntrySet { + @Override + public int size() { + return map.size(); + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + final ImmutableList> entryList = map.entrySet().asList(); + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + return entryFromMapEntry(entryList.get(index)); + } + + @Override + ImmutableCollection> delegateCollection() { + return EntrySet.this; + } + }; + } + } + + @Override + public int hashCode() { + return map.hashCode(); + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableSet.java b/guava/src/com/google/common/collect/RegularImmutableSet.java new file mode 100644 index 0000000..75a49c1 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet.ArrayImmutableSet; + +/** + * Implementation of {@link ImmutableSet} with two or more elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class RegularImmutableSet extends ArrayImmutableSet { + // the same elements in hashed positions (plus nulls) + @VisibleForTesting final transient Object[] table; + // 'and' with an int to get a valid table index. + private final transient int mask; + private final transient int hashCode; + + RegularImmutableSet( + Object[] elements, int hashCode, Object[] table, int mask) { + super(elements); + this.table = table; + this.mask = mask; + this.hashCode = hashCode; + } + + @Override public boolean contains(Object target) { + if (target == null) { + return false; + } + for (int i = Hashing.smear(target.hashCode()); true; i++) { + Object candidate = table[i & mask]; + if (candidate == null) { + return false; + } + if (candidate.equals(target)) { + return true; + } + } + } + + @Override public int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableSortedMap.java b/guava/src/com/google/common/collect/RegularImmutableSortedMap.java new file mode 100644 index 0000000..ef04429 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableSortedMap.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nullable; + +/** + * An implementation of an immutable sorted map with one or more entries. + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +final class RegularImmutableSortedMap extends ImmutableSortedMap { + private final transient RegularImmutableSortedSet keySet; + private final transient ImmutableList valueList; + + RegularImmutableSortedMap(RegularImmutableSortedSet keySet, ImmutableList valueList) { + this.keySet = keySet; + this.valueList = valueList; + } + + RegularImmutableSortedMap( + RegularImmutableSortedSet keySet, + ImmutableList valueList, + ImmutableSortedMap descendingMap) { + super(descendingMap); + this.keySet = keySet; + this.valueList = valueList; + } + + @Override + ImmutableSet> createEntrySet() { + return new EntrySet(); + } + + private class EntrySet extends ImmutableMapEntrySet { + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + // avoid additional indirection + private final ImmutableList keyList = keySet().asList(); + private final ImmutableList valueList = values().asList(); + + @Override + public Entry get(int index) { + return Maps.immutableEntry(keyList.get(index), valueList.get(index)); + } + + @Override + ImmutableCollection> delegateCollection() { + return EntrySet.this; + } + }; + } + + @Override + ImmutableMap map() { + return RegularImmutableSortedMap.this; + } + } + + @Override + public ImmutableSortedSet keySet() { + return keySet; + } + + @Override + public ImmutableCollection values() { + return valueList; + } + + @Override + public V get(@Nullable Object key) { + int index = keySet.indexOf(key); + return (index == -1) ? null : valueList.get(index); + } + + private ImmutableSortedMap getSubMap(int fromIndex, int toIndex) { + if (fromIndex == 0 && toIndex == size()) { + return this; + } else if (fromIndex == toIndex) { + return emptyMap(comparator()); + } else { + return from( + keySet.getSubSet(fromIndex, toIndex), + valueList.subList(fromIndex, toIndex)); + } + } + + @Override + public ImmutableSortedMap headMap(K toKey, boolean inclusive) { + return getSubMap(0, keySet.headIndex(checkNotNull(toKey), inclusive)); + } + + @Override + public ImmutableSortedMap tailMap(K fromKey, boolean inclusive) { + return getSubMap(keySet.tailIndex(checkNotNull(fromKey), inclusive), size()); + } + + @Override + ImmutableSortedMap createDescendingMap() { + return new RegularImmutableSortedMap( + (RegularImmutableSortedSet) keySet.descendingSet(), + valueList.reverse(), + this); + } + +} diff --git a/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java b/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java new file mode 100644 index 0000000..3db3405 --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.collect.BoundType.CLOSED; + +import com.google.common.primitives.Ints; + +import javax.annotation.Nullable; + +/** + * An immutable sorted multiset with one or more distinct elements. + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +final class RegularImmutableSortedMultiset extends ImmutableSortedMultiset { + private final transient RegularImmutableSortedSet elementSet; + private final transient int[] counts; + private final transient long[] cumulativeCounts; + private final transient int offset; + private final transient int length; + + RegularImmutableSortedMultiset( + RegularImmutableSortedSet elementSet, + int[] counts, + long[] cumulativeCounts, + int offset, + int length) { + this.elementSet = elementSet; + this.counts = counts; + this.cumulativeCounts = cumulativeCounts; + this.offset = offset; + this.length = length; + } + + private Entry getEntry(int index) { + return Multisets.immutableEntry( + elementSet.asList().get(index), + counts[offset + index]); + } + + @Override + public Entry firstEntry() { + return getEntry(0); + } + + @Override + public Entry lastEntry() { + return getEntry(length - 1); + } + + @Override + public int count(@Nullable Object element) { + int index = elementSet.indexOf(element); + return (index == -1) ? 0 : counts[index + offset]; + } + + @Override + public int size() { + long size = cumulativeCounts[offset + length] - cumulativeCounts[offset]; + return Ints.saturatedCast(size); + } + + @Override + public ImmutableSortedSet elementSet() { + return elementSet; + } + + @Override + public ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType) { + return getSubMultiset(0, elementSet.headIndex(upperBound, checkNotNull(boundType) == CLOSED)); + } + + @Override + public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return getSubMultiset(elementSet.tailIndex(lowerBound, checkNotNull(boundType) == CLOSED), + length); + } + + ImmutableSortedMultiset getSubMultiset(int from, int to) { + checkPositionIndexes(from, to, length); + if (from == to) { + return emptyMultiset(comparator()); + } else if (from == 0 && to == length) { + return this; + } else { + RegularImmutableSortedSet subElementSet = + (RegularImmutableSortedSet) elementSet.getSubSet(from, to); + return new RegularImmutableSortedMultiset( + subElementSet, counts, cumulativeCounts, offset + from, to - from); + } + } + + @Override + ImmutableSet> createEntrySet() { + return new EntrySet(); + } + + private final class EntrySet extends ImmutableMultiset.EntrySet { + @Override + public int size() { + return length; + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + return getEntry(index); + } + + @Override + ImmutableCollection> delegateCollection() { + return EntrySet.this; + } + }; + } + } + + @Override + boolean isPartialView() { + return offset > 0 || length < counts.length; + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableSortedSet.java b/guava/src/com/google/common/collect/RegularImmutableSortedSet.java new file mode 100644 index 0000000..eef64ed --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableSortedSet.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.SortedLists.KeyAbsentBehavior.INVERTED_INSERTION_INDEX; +import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_HIGHER; +import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT; +import static com.google.common.collect.SortedLists.KeyPresentBehavior.FIRST_AFTER; +import static com.google.common.collect.SortedLists.KeyPresentBehavior.FIRST_PRESENT; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An immutable sorted set with one or more elements. TODO(jlevy): Consider + * separate class for a single-element sorted set. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") +final class RegularImmutableSortedSet extends ImmutableSortedSet { + + private transient final ImmutableList elements; + + RegularImmutableSortedSet( + ImmutableList elements, Comparator comparator) { + super(comparator); + this.elements = elements; + checkArgument(!elements.isEmpty()); + } + + @Override public UnmodifiableIterator iterator() { + return elements.iterator(); + } + + @Override public boolean isEmpty() { + return false; + } + + @Override + public int size() { + return elements.size(); + } + + @Override public boolean contains(Object o) { + if (o == null) { + return false; + } + try { + return unsafeBinarySearch(o) >= 0; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean containsAll(Collection targets) { + // TODO(jlevy): For optimal performance, use a binary search when + // targets.size() < size() / log(size()) + // TODO(kevinb): see if we can share code with OrderedIterator after it + // graduates from labs. + if (!SortedIterables.hasSameComparator(comparator(), targets) + || (targets.size() <= 1)) { + return super.containsAll(targets); + } + + /* + * If targets is a sorted set with the same comparator, containsAll can run + * in O(n) time stepping through the two collections. + */ + Iterator thisIterator = iterator(); + Iterator thatIterator = targets.iterator(); + Object target = thatIterator.next(); + + try { + + while (thisIterator.hasNext()) { + + int cmp = unsafeCompare(thisIterator.next(), target); + + if (cmp == 0) { + + if (!thatIterator.hasNext()) { + + return true; + } + + target = thatIterator.next(); + + } else if (cmp > 0) { + return false; + } + } + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + + return false; + } + + private int unsafeBinarySearch(Object key) throws ClassCastException { + return Collections.binarySearch(elements, key, unsafeComparator()); + } + + @Override boolean isPartialView() { + return elements.isPartialView(); + } + + @Override public Object[] toArray() { + return elements.toArray(); + } + + @Override public T[] toArray(T[] array) { + return elements.toArray(array); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set)) { + return false; + } + + Set that = (Set) object; + if (size() != that.size()) { + return false; + } + + if (SortedIterables.hasSameComparator(comparator, that)) { + Iterator otherIterator = that.iterator(); + try { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + Object element = iterator.next(); + Object otherElement = otherIterator.next(); + if (otherElement == null + || unsafeCompare(element, otherElement) != 0) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; // concurrent change to other set + } + } + return this.containsAll(that); + } + + @Override + public E first() { + return elements.get(0); + } + + @Override + public E last() { + return elements.get(size() - 1); + } + + @Override + ImmutableSortedSet headSetImpl(E toElement, boolean inclusive) { + return getSubSet(0, headIndex(toElement, inclusive)); + } + + int headIndex(E toElement, boolean inclusive) { + return SortedLists.binarySearch( + elements, checkNotNull(toElement), comparator(), + inclusive ? FIRST_AFTER : FIRST_PRESENT, NEXT_HIGHER); + } + + @Override + ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return tailSetImpl(fromElement, fromInclusive) + .headSetImpl(toElement, toInclusive); + } + + @Override + ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive) { + return getSubSet(tailIndex(fromElement, inclusive), size()); + } + + int tailIndex(E fromElement, boolean inclusive) { + return SortedLists.binarySearch( + elements, + checkNotNull(fromElement), + comparator(), + inclusive ? FIRST_PRESENT : FIRST_AFTER, NEXT_HIGHER); + } + + // Pretend the comparator can compare anything. If it turns out it can't + // compare two elements, it'll throw a CCE. Only methods that are specified to + // throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator() { + return (Comparator) comparator; + } + + ImmutableSortedSet getSubSet(int newFromIndex, int newToIndex) { + if (newFromIndex == 0 && newToIndex == size()) { + return this; + } else if (newFromIndex < newToIndex) { + return new RegularImmutableSortedSet( + elements.subList(newFromIndex, newToIndex), comparator); + } else { + return emptySet(comparator); + } + } + + @Override int indexOf(@Nullable Object target) { + if (target == null) { + return -1; + } + int position; + try { + position = SortedLists.binarySearch(elements, target, unsafeComparator(), + ANY_PRESENT, INVERTED_INSERTION_INDEX); + } catch (ClassCastException e) { + return -1; + } + return (position >= 0) ? position : -1; + } + + @Override ImmutableList createAsList() { + return new ImmutableSortedAsList(this, elements); + } + + @Override + ImmutableSortedSet createDescendingSet() { + return new RegularImmutableSortedSet(elements.reverse(), + Ordering.from(comparator).reverse()); + } +} diff --git a/guava/src/com/google/common/collect/RegularImmutableTable.java b/guava/src/com/google/common/collect/RegularImmutableTable.java new file mode 100644 index 0000000..7245c9a --- /dev/null +++ b/guava/src/com/google/common/collect/RegularImmutableTable.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Objects; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * An implementation of {@link ImmutableTable} holding an arbitrary number of + * cells. + * + * @author Gregory Kick + */ +@GwtCompatible +abstract class RegularImmutableTable extends ImmutableTable { + // TODO(user): split DenseImmutableTable, SparseImmutableTable into their own classes + private final ImmutableSet> cellSet; + + private RegularImmutableTable(ImmutableSet> cellSet) { + this.cellSet = cellSet; + } + + private static final Function, Object> + GET_VALUE_FUNCTION = + new Function, Object>() { + @Override public Object apply(Cell from) { + return from.getValue(); + } + }; + + @SuppressWarnings("unchecked") + private Function, V> getValueFunction() { + return (Function) GET_VALUE_FUNCTION; + } + + @Nullable private transient volatile ImmutableList valueList; + + @Override public final ImmutableCollection values() { + ImmutableList result = valueList; + if (result == null) { + valueList = result = ImmutableList.copyOf( + Iterables.transform(cellSet(), getValueFunction())); + } + return result; + } + + @Override public final int size() { + return cellSet().size(); + } + + @Override public final boolean containsValue(@Nullable Object value) { + return values().contains(value); + } + + @Override public final boolean isEmpty() { + return false; + } + + @Override public final ImmutableSet> cellSet() { + return cellSet; + } + + static final RegularImmutableTable forCells( + List> cells, + @Nullable final Comparator rowComparator, + @Nullable final Comparator columnComparator) { + checkNotNull(cells); + if (rowComparator != null || columnComparator != null) { + /* + * This sorting logic leads to a cellSet() ordering that may not be + * expected and that isn't documented in the Javadoc. If a row Comparator + * is provided, cellSet() iterates across the columns in the first row, + * the columns in the second row, etc. If a column Comparator is provided + * but a row Comparator isn't, cellSet() iterates across the rows in the + * first column, the rows in the second column, etc. + */ + Comparator> comparator = new Comparator>() { + @Override public int compare(Cell cell1, Cell cell2) { + int rowCompare = (rowComparator == null) ? 0 + : rowComparator.compare(cell1.getRowKey(), cell2.getRowKey()); + if (rowCompare != 0) { + return rowCompare; + } + return (columnComparator == null) ? 0 + : columnComparator.compare( + cell1.getColumnKey(), cell2.getColumnKey()); + } + }; + Collections.sort(cells, comparator); + } + return forCellsInternal(cells, rowComparator, columnComparator); + } + + static final RegularImmutableTable forCells( + Iterable> cells) { + return forCellsInternal(cells, null, null); + } + + /** + * A factory that chooses the most space-efficient representation of the + * table. + */ + private static final RegularImmutableTable + forCellsInternal(Iterable> cells, + @Nullable Comparator rowComparator, + @Nullable Comparator columnComparator) { + ImmutableSet.Builder> cellSetBuilder = ImmutableSet.builder(); + ImmutableSet.Builder rowSpaceBuilder = ImmutableSet.builder(); + ImmutableSet.Builder columnSpaceBuilder = ImmutableSet.builder(); + for (Cell cell : cells) { + cellSetBuilder.add(cell); + rowSpaceBuilder.add(cell.getRowKey()); + columnSpaceBuilder.add(cell.getColumnKey()); + } + ImmutableSet> cellSet = cellSetBuilder.build(); + + ImmutableSet rowSpace = rowSpaceBuilder.build(); + if (rowComparator != null) { + List rowList = Lists.newArrayList(rowSpace); + Collections.sort(rowList, rowComparator); + rowSpace = ImmutableSet.copyOf(rowList); + } + ImmutableSet columnSpace = columnSpaceBuilder.build(); + if (columnComparator != null) { + List columnList = Lists.newArrayList(columnSpace); + Collections.sort(columnList, columnComparator); + columnSpace = ImmutableSet.copyOf(columnList); + } + + // use a dense table if more than half of the cells have values + // TODO(gak): tune this condition based on empirical evidence + return (cellSet.size() > ((rowSpace.size() * columnSpace.size()) / 2 )) ? + new DenseImmutableTable(cellSet, rowSpace, columnSpace) : + new SparseImmutableTable(cellSet, rowSpace, columnSpace); + } + + /** + * A {@code RegularImmutableTable} optimized for sparse data. + */ + @Immutable + @VisibleForTesting + static final class SparseImmutableTable + extends RegularImmutableTable { + + private final ImmutableMap> rowMap; + private final ImmutableMap> columnMap; + + /** + * Creates a {@link Map} over the key space with + * {@link ImmutableMap.Builder}s ready for values. + */ + private static final Map> + makeIndexBuilder(ImmutableSet keySpace) { + Map> indexBuilder = Maps.newLinkedHashMap(); + for (A key : keySpace) { + indexBuilder.put(key, ImmutableMap.builder()); + } + return indexBuilder; + } + + /** + * Builds the value maps of the index and creates an immutable copy of the + * map. + */ + private static final ImmutableMap> buildIndex( + Map> indexBuilder) { + return ImmutableMap.copyOf(Maps.transformValues(indexBuilder, + new Function, Map>() { + @Override public Map apply(ImmutableMap.Builder from) { + return from.build(); + } + })); + } + + SparseImmutableTable(ImmutableSet> cellSet, + ImmutableSet rowSpace, ImmutableSet columnSpace) { + super(cellSet); + Map> rowIndexBuilder + = makeIndexBuilder(rowSpace); + Map> columnIndexBuilder + = makeIndexBuilder(columnSpace); + for (Cell cell : cellSet) { + R rowKey = cell.getRowKey(); + C columnKey = cell.getColumnKey(); + V value = cell.getValue(); + rowIndexBuilder.get(rowKey).put(columnKey, value); + columnIndexBuilder.get(columnKey).put(rowKey, value); + } + this.rowMap = buildIndex(rowIndexBuilder); + this.columnMap = buildIndex(columnIndexBuilder); + } + + @Override public ImmutableMap column(C columnKey) { + checkNotNull(columnKey); + // value maps are guaranteed to be immutable maps + return Objects.firstNonNull((ImmutableMap) columnMap.get(columnKey), + ImmutableMap.of()); + } + + @Override public ImmutableSet columnKeySet() { + return columnMap.keySet(); + } + + @Override public ImmutableMap> columnMap() { + return columnMap; + } + + @Override public ImmutableMap row(R rowKey) { + checkNotNull(rowKey); + // value maps are guaranteed to be immutable maps + return Objects.firstNonNull((ImmutableMap) rowMap.get(rowKey), + ImmutableMap.of()); + } + + @Override public ImmutableSet rowKeySet() { + return rowMap.keySet(); + } + + @Override public ImmutableMap> rowMap() { + return rowMap; + } + + @Override public boolean contains(@Nullable Object rowKey, + @Nullable Object columnKey) { + Map row = rowMap.get(rowKey); + return (row != null) && row.containsKey(columnKey); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return columnMap.containsKey(columnKey); + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return rowMap.containsKey(rowKey); + } + + @Override public V get(@Nullable Object rowKey, + @Nullable Object columnKey) { + Map row = rowMap.get(rowKey); + return (row == null) ? null : row.get(columnKey); + } + } + + /** + * An immutable map implementation backed by an indexed nullable array, used in + * DenseImmutableTable. + */ + private abstract static class ImmutableArrayMap extends ImmutableMap { + private final int size; + + ImmutableArrayMap(int size) { + this.size = size; + } + + abstract ImmutableMap keyToIndex(); + + // True if getValue never returns null. + private boolean isFull() { + return size == keyToIndex().size(); + } + + K getKey(int index) { + return keyToIndex().keySet().asList().get(index); + } + + @Nullable abstract V getValue(int keyIndex); + + @Override + ImmutableSet createKeySet() { + return isFull() ? keyToIndex().keySet() : super.createKeySet(); + } + + @Override + public int size() { + return size; + } + + @Override + public V get(@Nullable Object key) { + Integer keyIndex = keyToIndex().get(key); + return (keyIndex == null) ? null : getValue(keyIndex); + } + + @Override + ImmutableSet> createEntrySet() { + if (isFull()) { + return new ImmutableMapEntrySet() { + @Override ImmutableMap map() { + return ImmutableArrayMap.this; + } + + @Override + public UnmodifiableIterator> iterator() { + return new AbstractIndexedListIterator>(size()) { + @Override + protected Entry get(int index) { + return Maps.immutableEntry(getKey(index), getValue(index)); + } + }; + } + }; + } else { + return new ImmutableMapEntrySet() { + @Override ImmutableMap map() { + return ImmutableArrayMap.this; + } + + @Override + public UnmodifiableIterator> iterator() { + return new AbstractIterator>() { + private int index = -1; + private final int maxIndex = keyToIndex().size(); + + @Override + protected Entry computeNext() { + for (index++; index < maxIndex; index++) { + V value = getValue(index); + if (value != null) { + return Maps.immutableEntry(getKey(index), value); + } + } + return endOfData(); + } + }; + } + }; + } + } + } + + /** + * A {@code RegularImmutableTable} optimized for dense data. + */ + @Immutable @VisibleForTesting + static final class DenseImmutableTable + extends RegularImmutableTable { + + private final ImmutableMap rowKeyToIndex; + private final ImmutableMap columnKeyToIndex; + private final ImmutableMap> rowMap; + private final ImmutableMap> columnMap; + private final int[] rowCounts; + private final int[] columnCounts; + private final V[][] values; + + private static ImmutableMap makeIndex( + ImmutableSet set) { + ImmutableMap.Builder indexBuilder = + ImmutableMap.builder(); + int i = 0; + for (E key : set) { + indexBuilder.put(key, i); + i++; + } + return indexBuilder.build(); + } + + DenseImmutableTable(ImmutableSet> cellSet, + ImmutableSet rowSpace, ImmutableSet columnSpace) { + super(cellSet); + @SuppressWarnings("unchecked") + V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()]; + this.values = array; + this.rowKeyToIndex = makeIndex(rowSpace); + this.columnKeyToIndex = makeIndex(columnSpace); + rowCounts = new int[rowKeyToIndex.size()]; + columnCounts = new int[columnKeyToIndex.size()]; + for (Cell cell : cellSet) { + R rowKey = cell.getRowKey(); + C columnKey = cell.getColumnKey(); + int rowIndex = rowKeyToIndex.get(rowKey); + int columnIndex = columnKeyToIndex.get(columnKey); + V existingValue = values[rowIndex][columnIndex]; + checkArgument(existingValue == null, "duplicate key: (%s, %s)", rowKey, + columnKey); + values[rowIndex][columnIndex] = cell.getValue(); + rowCounts[rowIndex]++; + columnCounts[columnIndex]++; + } + + this.rowMap = new RowMap(); + this.columnMap = new ColumnMap(); + } + + private final class Row extends ImmutableArrayMap { + private final int rowIndex; + + Row(int rowIndex) { + super(rowCounts[rowIndex]); + this.rowIndex = rowIndex; + } + + @Override + ImmutableMap keyToIndex() { + return columnKeyToIndex; + } + + @Override + V getValue(int keyIndex) { + return values[rowIndex][keyIndex]; + } + + @Override + boolean isPartialView() { + return true; + } + } + + private final class Column extends ImmutableArrayMap { + private final int columnIndex; + + Column(int columnIndex) { + super(columnCounts[columnIndex]); + this.columnIndex = columnIndex; + } + + @Override + ImmutableMap keyToIndex() { + return rowKeyToIndex; + } + + @Override + V getValue(int keyIndex) { + return values[keyIndex][columnIndex]; + } + + @Override + boolean isPartialView() { + return true; + } + } + + private final class RowMap extends ImmutableArrayMap> { + private RowMap() { + super(rowCounts.length); + } + + @Override + ImmutableMap keyToIndex() { + return rowKeyToIndex; + } + + @Override + Map getValue(int keyIndex) { + return new Row(keyIndex); + } + + @Override + boolean isPartialView() { + return false; + } + } + + private final class ColumnMap extends ImmutableArrayMap> { + private ColumnMap() { + super(columnCounts.length); + } + + @Override + ImmutableMap keyToIndex() { + return columnKeyToIndex; + } + + @Override + Map getValue(int keyIndex) { + return new Column(keyIndex); + } + + @Override + boolean isPartialView() { + return false; + } + } + + @Override public ImmutableMap column(C columnKey) { + Integer columnIndex = columnKeyToIndex.get(checkNotNull(columnKey)); + if (columnIndex == null) { + return ImmutableMap.of(); + } else { + return new Column(columnIndex); + } + } + + @Override public ImmutableSet columnKeySet() { + return columnKeyToIndex.keySet(); + } + + @Override public ImmutableMap> columnMap() { + return columnMap; + } + + @Override public boolean contains(@Nullable Object rowKey, + @Nullable Object columnKey) { + return (get(rowKey, columnKey) != null); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return columnKeyToIndex.containsKey(columnKey); + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return rowKeyToIndex.containsKey(rowKey); + } + + @Override public V get(@Nullable Object rowKey, + @Nullable Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return ((rowIndex == null) || (columnIndex == null)) ? null + : values[rowIndex][columnIndex]; + } + + @Override public ImmutableMap row(R rowKey) { + checkNotNull(rowKey); + Integer rowIndex = rowKeyToIndex.get(rowKey); + if (rowIndex == null) { + return ImmutableMap.of(); + } else { + return new Row(rowIndex); + } + } + + @Override public ImmutableSet rowKeySet() { + return rowKeyToIndex.keySet(); + } + + @Override + public ImmutableMap> rowMap() { + return rowMap; + } + } +} diff --git a/guava/src/com/google/common/collect/ReverseNaturalOrdering.java b/guava/src/com/google/common/collect/ReverseNaturalOrdering.java new file mode 100644 index 0000000..9a73613 --- /dev/null +++ b/guava/src/com/google/common/collect/ReverseNaturalOrdering.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Iterator; + +/** An ordering that uses the reverse of the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? +final class ReverseNaturalOrdering + extends Ordering implements Serializable { + static final ReverseNaturalOrdering INSTANCE = new ReverseNaturalOrdering(); + + @Override public int compare(Comparable left, Comparable right) { + checkNotNull(left); // right null is caught later + if (left == right) { + return 0; + } + + return right.compareTo(left); + } + + @Override public Ordering reverse() { + return Ordering.natural(); + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override public E min(E a, E b) { + return NaturalOrdering.INSTANCE.max(a, b); + } + + @Override public E min(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.max(a, b, c, rest); + } + + @Override public E min(Iterator iterator) { + return NaturalOrdering.INSTANCE.max(iterator); + } + + @Override public E min(Iterable iterable) { + return NaturalOrdering.INSTANCE.max(iterable); + } + + @Override public E max(E a, E b) { + return NaturalOrdering.INSTANCE.min(a, b); + } + + @Override public E max(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.min(a, b, c, rest); + } + + @Override public E max(Iterator iterator) { + return NaturalOrdering.INSTANCE.min(iterator); + } + + @Override public E max(Iterable iterable) { + return NaturalOrdering.INSTANCE.min(iterable); + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.natural().reverse()"; + } + + private ReverseNaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/ReverseOrdering.java b/guava/src/com/google/common/collect/ReverseOrdering.java new file mode 100644 index 0000000..36ae02c --- /dev/null +++ b/guava/src/com/google/common/collect/ReverseOrdering.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Iterator; + +import javax.annotation.Nullable; + +/** An ordering that uses the reverse of a given order. */ +@GwtCompatible(serializable = true) +final class ReverseOrdering extends Ordering implements Serializable { + final Ordering forwardOrder; + + ReverseOrdering(Ordering forwardOrder) { + this.forwardOrder = checkNotNull(forwardOrder); + } + + @Override public int compare(T a, T b) { + return forwardOrder.compare(b, a); + } + + @SuppressWarnings("unchecked") // how to explain? + @Override public Ordering reverse() { + return (Ordering) forwardOrder; + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override public E min(E a, E b) { + return forwardOrder.max(a, b); + } + + @Override public E min(E a, E b, E c, E... rest) { + return forwardOrder.max(a, b, c, rest); + } + + @Override public E min(Iterator iterator) { + return forwardOrder.max(iterator); + } + + @Override public E min(Iterable iterable) { + return forwardOrder.max(iterable); + } + + @Override public E max(E a, E b) { + return forwardOrder.min(a, b); + } + + @Override public E max(E a, E b, E c, E... rest) { + return forwardOrder.min(a, b, c, rest); + } + + @Override public E max(Iterator iterator) { + return forwardOrder.min(iterator); + } + + @Override public E max(Iterable iterable) { + return forwardOrder.min(iterable); + } + + @Override public int hashCode() { + return -forwardOrder.hashCode(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ReverseOrdering) { + ReverseOrdering that = (ReverseOrdering) object; + return this.forwardOrder.equals(that.forwardOrder); + } + return false; + } + + @Override public String toString() { + return forwardOrder + ".reverse()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/RowSortedTable.java b/guava/src/com/google/common/collect/RowSortedTable.java new file mode 100644 index 0000000..4b597b7 --- /dev/null +++ b/guava/src/com/google/common/collect/RowSortedTable.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Interface that extends {@code Table} and whose rows are sorted. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link + * #rowMap} method returns a {@link SortedMap}, instead of the {@link Set} and + * {@link Map} specified by the {@link Table} interface. + * + * @author Warren Dukes + * @since 8.0 + */ +@GwtCompatible +@Beta +public interface RowSortedTable extends Table { + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedSet}, instead of the {@code Set} + * specified in the {@link Table} interface. + */ + @Override SortedSet rowKeySet(); + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedMap}, instead of the {@code Map} + * specified in the {@link Table} interface. + */ + @Override SortedMap> rowMap(); +} diff --git a/guava/src/com/google/common/collect/Serialization.java b/guava/src/com/google/common/collect/Serialization.java new file mode 100644 index 0000000..2541548 --- /dev/null +++ b/guava/src/com/google/common/collect/Serialization.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; + +/** + * Provides static methods for serializing collection classes. + * + *

This class assists the implementation of collection classes. Do not use + * this class to serialize collections that are defined elsewhere. + * + * @author Jared Levy + */ +final class Serialization { + private Serialization() {} + + /** + * Reads a count corresponding to a serialized map, multiset, or multimap. It + * returns the size of a map serialized by {@link + * #writeMap(Map, ObjectOutputStream)}, the number of distinct elements in a + * multiset serialized by {@link + * #writeMultiset(Multiset, ObjectOutputStream)}, or the number of distinct + * keys in a multimap serialized by {@link + * #writeMultimap(Multimap, ObjectOutputStream)}. + * + *

The returned count may be used to construct an empty collection of the + * appropriate capacity before calling any of the {@code populate} methods. + */ + static int readCount(ObjectInputStream stream) throws IOException { + return stream.readInt(); + } + + /** + * Stores the contents of a map in an output stream, as part of serialization. + * It does not support concurrent maps whose content may change while the + * method is running. + * + *

The serialized output consists of the number of entries, first key, + * first value, second key, second value, and so on. + */ + static void writeMap(Map map, ObjectOutputStream stream) + throws IOException { + stream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + /** + * Populates a map by reading an input stream, as part of deserialization. + * See {@link #writeMap} for the data format. + */ + static void populateMap(Map map, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int size = stream.readInt(); + populateMap(map, stream, size); + } + + /** + * Populates a map by reading an input stream, as part of deserialization. + * See {@link #writeMap} for the data format. The size is determined by a + * prior call to {@link #readCount}. + */ + static void populateMap(Map map, ObjectInputStream stream, + int size) throws IOException, ClassNotFoundException { + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMap + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeMap + V value = (V) stream.readObject(); + map.put(key, value); + } + } + + /** + * Stores the contents of a multiset in an output stream, as part of + * serialization. It does not support concurrent multisets whose content may + * change while the method is running. + * + *

The serialized output consists of the number of distinct elements, the + * first element, its count, the second element, its count, and so on. + */ + static void writeMultiset( + Multiset multiset, ObjectOutputStream stream) throws IOException { + int entryCount = multiset.entrySet().size(); + stream.writeInt(entryCount); + for (Multiset.Entry entry : multiset.entrySet()) { + stream.writeObject(entry.getElement()); + stream.writeInt(entry.getCount()); + } + } + + /** + * Populates a multiset by reading an input stream, as part of + * deserialization. See {@link #writeMultiset} for the data format. + */ + static void populateMultiset( + Multiset multiset, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctElements = stream.readInt(); + populateMultiset(multiset, stream, distinctElements); + } + + /** + * Populates a multiset by reading an input stream, as part of + * deserialization. See {@link #writeMultiset} for the data format. The number + * of distinct elements is determined by a prior call to {@link #readCount}. + */ + static void populateMultiset( + Multiset multiset, ObjectInputStream stream, int distinctElements) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctElements; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + multiset.add(element, count); + } + } + + /** + * Stores the contents of a multimap in an output stream, as part of + * serialization. It does not support concurrent multimaps whose content may + * change while the method is running. The {@link Multimap#asMap} view + * determines the ordering in which data is written to the stream. + * + *

The serialized output consists of the number of distinct keys, and then + * for each distinct key: the key, the number of values for that key, and the + * key's values. + */ + static void writeMultimap( + Multimap multimap, ObjectOutputStream stream) throws IOException { + stream.writeInt(multimap.asMap().size()); + for (Map.Entry> entry : multimap.asMap().entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeInt(entry.getValue().size()); + for (V value : entry.getValue()) { + stream.writeObject(value); + } + } + } + + /** + * Populates a multimap by reading an input stream, as part of + * deserialization. See {@link #writeMultimap} for the data format. + */ + static void populateMultimap( + Multimap multimap, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctKeys = stream.readInt(); + populateMultimap(multimap, stream, distinctKeys); + } + + /** + * Populates a multimap by reading an input stream, as part of + * deserialization. See {@link #writeMultimap} for the data format. The number + * of distinct keys is determined by a prior call to {@link #readCount}. + */ + static void populateMultimap( + Multimap multimap, ObjectInputStream stream, int distinctKeys) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + Collection values = multimap.get(key); + int valueCount = stream.readInt(); + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values.add(value); + } + } + } + + // Secret sauce for setting final fields; don't make it public. + static FieldSetter getFieldSetter( + final Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + return new FieldSetter(field); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); // programmer error + } + } + + // Secret sauce for setting final fields; don't make it public. + static final class FieldSetter { + private final Field field; + + private FieldSetter(Field field) { + this.field = field; + field.setAccessible(true); + } + + void set(T instance, Object value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + + void set(T instance, int value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + } +} diff --git a/guava/src/com/google/common/collect/SetMultimap.java b/guava/src/com/google/common/collect/SetMultimap.java new file mode 100644 index 0000000..18f4a18 --- /dev/null +++ b/guava/src/com/google/common/collect/SetMultimap.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a + * key-value pair that's already in the multimap has no effect. See the {@link + * Multimap} documentation for information common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link Set} of values, while {@link #entries} returns a {@code + * Set} of map entries. Though the method signature doesn't say so explicitly, + * the map returned by {@link #asMap} has {@code Set} values. + * + *

If the values corresponding to a single key should be ordered according to + * a {@link java.util.Comparator} (or the natural order), see the + * {@link SortedSetMultimap} subinterface. + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + @Override + Set get(@Nullable K key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + @Override + Set removeAll(@Nullable Object key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override + Set replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + @Override + Set> entries(); + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link Set} values. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + * + *

An empty {@code SetMultimap} is equal to any other empty {@code + * Multimap}, including an empty {@code ListMultimap}. + */ + @Override + boolean equals(@Nullable Object obj); +} diff --git a/guava/src/com/google/common/collect/Sets.java b/guava/src/com/google/common/collect/Sets.java new file mode 100644 index 0000000..016b3eb --- /dev/null +++ b/guava/src/com/google/common/collect/Sets.java @@ -0,0 +1,1666 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2.FilteredCollection; +import com.google.common.math.IntMath; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link Set} instances. Also see this + * class's counterparts {@link Lists} and {@link Maps}. + * + *

See the Guava User Guide article on + * {@code Sets}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @author Chris Povirk + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class Sets { + private Sets() {} + + /** + * {@link AbstractSet} substitute without the potentially-quadratic + * {@code removeAll} implementation. + */ + abstract static class ImprovedAbstractSet extends AbstractSet { + @Override + public boolean removeAll(Collection c) { + return removeAllImpl(this, c); + } + + @Override + public boolean retainAll(Collection c) { + return super.retainAll(checkNotNull(c)); // GWT compatibility + } + } + + /** + * Returns an immutable set instance containing the given enum elements. + * Internally, the returned set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration + * order, not the order in which the elements are provided to the method. + * + * @param anElement one of the elements the set should contain + * @param otherElements the rest of the elements the set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet( + E anElement, E... otherElements) { + return new ImmutableEnumSet(EnumSet.of(anElement, otherElements)); + } + + /** + * Returns an immutable set instance containing the given enum elements. + * Internally, the returned set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration + * order, not the order in which the elements appear in the given collection. + * + * @param elements the elements, all of the same {@code enum} type, that the + * set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet( + Iterable elements) { + Iterator iterator = elements.iterator(); + if (!iterator.hasNext()) { + return ImmutableSet.of(); + } + if (elements instanceof EnumSet) { + EnumSet enumSetClone = EnumSet.copyOf((EnumSet) elements); + return new ImmutableEnumSet(enumSetClone); + } + E first = iterator.next(); + EnumSet set = EnumSet.of(first); + while (iterator.hasNext()) { + set.add(iterator.next()); + } + return new ImmutableEnumSet(set); + } + + /** + * Returns a new {@code EnumSet} instance containing the given elements. + * Unlike {@link EnumSet#copyOf(Collection)}, this method does not produce an + * exception on an empty collection, and it may be called on any iterable, not + * just a {@code Collection}. + */ + public static > EnumSet newEnumSet(Iterable iterable, + Class elementType) { + /* + * TODO(cpovirk): noneOf() and addAll() will both throw + * NullPointerExceptions when appropriate. However, NullPointerTester will + * fail on this method because it passes in Class.class instead of an enum + * type. This means that, when iterable is null but elementType is not, + * noneOf() will throw a ClassCastException before addAll() has a chance to + * throw a NullPointerException. NullPointerTester considers this a failure. + * Ideally the test would be fixed, but it would require a special case for + * Class where E extends Enum. Until that happens (if ever), leave + * checkNotNull() here. For now, contemplate the irony that checking + * elementType, the problem argument, is harmful, while checking iterable, + * the innocent bystander, is effective. + */ + checkNotNull(iterable); + EnumSet set = EnumSet.noneOf(elementType); + Iterables.addAll(set, iterable); + return set; + } + + // HashSet + + /** + * Creates a mutable, empty {@code HashSet} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableSet#of()} instead. + * + *

Note: if {@code E} is an {@link Enum} type, use {@link + * EnumSet#noneOf} instead. + * + * @return a new, empty {@code HashSet} + */ + public static HashSet newHashSet() { + return new HashSet(); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use an overload of {@link ImmutableSet#of()} (for varargs) or + * {@link ImmutableSet#copyOf(Object[])} (for an array) instead. + * + *

Note: if {@code E} is an {@link Enum} type, use {@link + * EnumSet#of(Enum, Enum[])} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(E... elements) { + HashSet set = newHashSetWithExpectedSize(elements.length); + Collections.addAll(set, elements); + return set; + } + + /** + * Creates a {@code HashSet} instance, with a high enough "initial capacity" + * that it should hold {@code expectedSize} elements without growth. + * This behavior cannot be broadly guaranteed, but it is observed to be true + * for OpenJDK 1.6. It also can't be guaranteed that the method isn't + * inadvertently oversizing the returned set. + * + * @param expectedSize the number of elements you expect to add to the + * returned set + * @return a new, empty {@code HashSet} with enough capacity to hold {@code + * expectedSize} elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashSet newHashSetWithExpectedSize(int expectedSize) { + return new HashSet(Maps.capacity(expectedSize)); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + *

Note: if {@code E} is an {@link Enum} type, use + * {@link #newEnumSet(Iterable, Class)} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(Iterable elements) { + return (elements instanceof Collection) + ? new HashSet(Collections2.cast(elements)) + : newHashSet(elements.iterator()); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + *

Note: if {@code E} is an {@link Enum} type, you should create an + * {@link EnumSet} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(Iterator elements) { + HashSet set = newHashSet(); + while (elements.hasNext()) { + set.add(elements.next()); + } + return set; + } + + // LinkedHashSet + + /** + * Creates a mutable, empty {@code LinkedHashSet} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableSet#of()} instead. + * + * @return a new, empty {@code LinkedHashSet} + */ + public static LinkedHashSet newLinkedHashSet() { + return new LinkedHashSet(); + } + + /** + * Creates a {@code LinkedHashSet} instance, with a high enough "initial + * capacity" that it should hold {@code expectedSize} elements without + * growth. This behavior cannot be broadly guaranteed, but it is observed to + * be true for OpenJDK 1.6. It also can't be guaranteed that the method isn't + * inadvertently oversizing the returned set. + * + * @param expectedSize the number of elements you expect to add to the + * returned set + * @return a new, empty {@code LinkedHashSet} with enough capacity to hold + * {@code expectedSize} elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + * @since 11.0 + */ + public static LinkedHashSet newLinkedHashSetWithExpectedSize( + int expectedSize) { + return new LinkedHashSet(Maps.capacity(expectedSize)); + } + + /** + * Creates a mutable {@code LinkedHashSet} instance containing the + * given elements in order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + * @param elements the elements that the set should contain, in order + * @return a new {@code LinkedHashSet} containing those elements (minus + * duplicates) + */ + public static LinkedHashSet newLinkedHashSet( + Iterable elements) { + if (elements instanceof Collection) { + return new LinkedHashSet(Collections2.cast(elements)); + } + LinkedHashSet set = newLinkedHashSet(); + for (E element : elements) { + set.add(element); + } + return set; + } + + // TreeSet + + /** + * Creates a mutable, empty {@code TreeSet} instance sorted by the + * natural sort ordering of its elements. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedSet#of()} instead. + * + * @return a new, empty {@code TreeSet} + */ + public static TreeSet newTreeSet() { + return new TreeSet(); + } + + /** + * Creates a mutable {@code TreeSet} instance containing the given + * elements sorted by their natural ordering. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedSet#copyOf(Iterable)} instead. + * + *

Note: If {@code elements} is a {@code SortedSet} with an explicit + * comparator, this method has different behavior than + * {@link TreeSet#TreeSet(SortedSet)}, which returns a {@code TreeSet} with + * that comparator. + * + * @param elements the elements that the set should contain + * @return a new {@code TreeSet} containing those elements (minus duplicates) + */ + public static TreeSet newTreeSet( + Iterable elements) { + TreeSet set = newTreeSet(); + for (E element : elements) { + set.add(element); + } + return set; + } + + /** + * Creates a mutable, empty {@code TreeSet} instance with the given + * comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedSet.orderedBy(comparator).build()} instead. + * + * @param comparator the comparator to use to sort the set + * @return a new, empty {@code TreeSet} + * @throws NullPointerException if {@code comparator} is null + */ + public static TreeSet newTreeSet(Comparator comparator) { + return new TreeSet(checkNotNull(comparator)); + } + + /** + * Creates an empty {@code Set} that uses identity to determine equality. It + * compares object references, instead of calling {@code equals}, to + * determine whether a provided object matches an element in the set. For + * example, {@code contains} returns {@code false} when passed an object that + * equals a set member, but isn't the same instance. This behavior is similar + * to the way {@code IdentityHashMap} handles key lookups. + * + * @since 8.0 + */ + public static Set newIdentityHashSet() { + return Sets.newSetFromMap(Maps.newIdentityHashMap()); + } + + /** + * Creates an empty {@code CopyOnWriteArraySet} instance. + * + *

Note: if you need an immutable empty {@link Set}, use + * {@link Collections#emptySet} instead. + * + * @return a new, empty {@code CopyOnWriteArraySet} + * @since 12.0 + */ + @GwtIncompatible("CopyOnWriteArraySet") + public static CopyOnWriteArraySet newCopyOnWriteArraySet() { + return new CopyOnWriteArraySet(); + } + + /** + * Creates a {@code CopyOnWriteArraySet} instance containing the given elements. + * + * @param elements the elements that the set should contain, in order + * @return a new {@code CopyOnWriteArraySet} containing those elements + * @since 12.0 + */ + @GwtIncompatible("CopyOnWriteArraySet") + public static CopyOnWriteArraySet newCopyOnWriteArraySet( + Iterable elements) { + // We copy elements to an ArrayList first, rather than incurring the + // quadratic cost of adding them to the COWAS directly. + Collection elementsCollection = (elements instanceof Collection) + ? Collections2.cast(elements) + : Lists.newArrayList(elements); + return new CopyOnWriteArraySet(elementsCollection); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in + * the specified collection. If the collection is an {@link EnumSet}, this + * method has the same behavior as {@link EnumSet#complementOf}. Otherwise, + * the specified collection must contain at least one element, in order to + * determine the element type. If the collection could be empty, use + * {@link #complementOf(Collection, Class)} instead of this method. + * + * @param collection the collection whose complement should be stored in the + * enum set + * @return a new, modifiable {@code EnumSet} containing all values of the enum + * that aren't present in the given collection + * @throws IllegalArgumentException if {@code collection} is not an + * {@code EnumSet} instance and contains no elements + */ + public static > EnumSet complementOf( + Collection collection) { + if (collection instanceof EnumSet) { + return EnumSet.complementOf((EnumSet) collection); + } + checkArgument(!collection.isEmpty(), + "collection is empty; use the other version of this method"); + Class type = collection.iterator().next().getDeclaringClass(); + return makeComplementByHand(collection, type); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in + * the specified collection. This is equivalent to + * {@link EnumSet#complementOf}, but can act on any input collection, as long + * as the elements are of enum type. + * + * @param collection the collection whose complement should be stored in the + * {@code EnumSet} + * @param type the type of the elements in the set + * @return a new, modifiable {@code EnumSet} initially containing all the + * values of the enum not present in the given collection + */ + public static > EnumSet complementOf( + Collection collection, Class type) { + checkNotNull(collection); + return (collection instanceof EnumSet) + ? EnumSet.complementOf((EnumSet) collection) + : makeComplementByHand(collection, type); + } + + private static > EnumSet makeComplementByHand( + Collection collection, Class type) { + EnumSet result = EnumSet.allOf(type); + result.removeAll(collection); + return result; + } + + /* + * Regarding newSetForMap() and SetFromMap: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + + /** + * Returns a set backed by the specified map. The resulting set displays + * the same ordering, concurrency, and performance characteristics as the + * backing map. In essence, this factory method provides a {@link Set} + * implementation corresponding to any {@link Map} implementation. There is no + * need to use this method on a {@link Map} implementation that already has a + * corresponding {@link Set} implementation (such as {@link java.util.HashMap} + * or {@link java.util.TreeMap}). + * + *

Each method invocation on the set returned by this method results in + * exactly one method invocation on the backing map or its {@code keySet} + * view, with one exception. The {@code addAll} method is implemented as a + * sequence of {@code put} invocations on the backing map. + * + *

The specified map must be empty at the time this method is invoked, + * and should not be accessed directly after this method returns. These + * conditions are ensured if the map is created empty, passed directly + * to this method, and no reference to the map is retained, as illustrated + * in the following code fragment:

  {@code
+   *
+   *   Set identityHashSet = Sets.newSetFromMap(
+   *       new IdentityHashMap());}
+   *
+   * This method has the same behavior as the JDK 6 method
+   * {@code Collections.newSetFromMap()}. The returned set is serializable if
+   * the backing map is.
+   *
+   * @param map the backing map
+   * @return the set backed by the map
+   * @throws IllegalArgumentException if {@code map} is not empty
+   */
+  public static  Set newSetFromMap(Map map) {
+    return new SetFromMap(map);
+  }
+
+  private static class SetFromMap extends AbstractSet
+      implements Set, Serializable {
+    private final Map m; // The backing map
+    private transient Set s; // Its keySet
+
+    SetFromMap(Map map) {
+      checkArgument(map.isEmpty(), "Map is non-empty");
+      m = map;
+      s = map.keySet();
+    }
+
+    @Override public void clear() {
+      m.clear();
+    }
+    @Override public int size() {
+      return m.size();
+    }
+    @Override public boolean isEmpty() {
+      return m.isEmpty();
+    }
+    @Override public boolean contains(Object o) {
+      return m.containsKey(o);
+    }
+    @Override public boolean remove(Object o) {
+      return m.remove(o) != null;
+    }
+    @Override public boolean add(E e) {
+      return m.put(e, Boolean.TRUE) == null;
+    }
+    @Override public Iterator iterator() {
+      return s.iterator();
+    }
+    @Override public Object[] toArray() {
+      return s.toArray();
+    }
+    @Override public  T[] toArray(T[] a) {
+      return s.toArray(a);
+    }
+    @Override public String toString() {
+      return s.toString();
+    }
+    @Override public int hashCode() {
+      return s.hashCode();
+    }
+    @Override public boolean equals(@Nullable Object object) {
+      return this == object || this.s.equals(object);
+    }
+    @Override public boolean containsAll(Collection c) {
+      return s.containsAll(c);
+    }
+    @Override public boolean removeAll(Collection c) {
+      return s.removeAll(c);
+    }
+    @Override public boolean retainAll(Collection c) {
+      return s.retainAll(c);
+    }
+
+    // addAll is the only inherited implementation
+    @GwtIncompatible("not needed in emulated source")
+    private static final long serialVersionUID = 0;
+
+    @GwtIncompatible("java.io.ObjectInputStream")
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException {
+      stream.defaultReadObject();
+      s = m.keySet();
+    }
+  }
+
+  /**
+   * An unmodifiable view of a set which may be backed by other sets; this view
+   * will change as the backing sets do. Contains methods to copy the data into
+   * a new set which will then remain stable. There is usually no reason to
+   * retain a reference of type {@code SetView}; typically, you either use it
+   * as a plain {@link Set}, or immediately invoke {@link #immutableCopy} or
+   * {@link #copyInto} and forget the {@code SetView} itself.
+   *
+   * @since 2.0 (imported from Google Collections Library)
+   */
+  public abstract static class SetView extends AbstractSet {
+    private SetView() {} // no subclasses but our own
+
+    /**
+     * Returns an immutable copy of the current contents of this set view.
+     * Does not support null elements.
+     *
+     * 

Warning: this may have unexpected results if a backing set of + * this view uses a nonstandard notion of equivalence, for example if it is + * a {@link TreeSet} using a comparator that is inconsistent with {@link + * Object#equals(Object)}. + */ + public ImmutableSet immutableCopy() { + return ImmutableSet.copyOf(this); + } + + /** + * Copies the current contents of this set view into an existing set. This + * method has equivalent behavior to {@code set.addAll(this)}, assuming that + * all the sets involved are based on the same notion of equivalence. + * + * @return a reference to {@code set}, for convenience + */ + // Note: S should logically extend Set but can't due to either + // some javac bug or some weirdness in the spec, not sure which. + public > S copyInto(S set) { + set.addAll(this); + return set; + } + } + + /** + * Returns an unmodifiable view of the union of two sets. The returned + * set contains all elements that are contained in either backing set. + * Iterating over the returned set iterates first over all the elements of + * {@code set1}, then over each element of {@code set2}, in order, that is not + * contained in {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on + * different equivalence relations (as {@link HashSet}, {@link TreeSet}, and + * the {@link Map#keySet} of an {@code IdentityHashMap} all are). + * + *

Note: The returned view performs better when {@code set1} is the + * smaller of the two sets. If you have reason to believe one of your sets + * will generally be smaller than the other, pass it first. + * + *

Further, note that the current implementation is not suitable for nested + * {@code union} views, i.e. the following should be avoided when in a loop: + * {@code union = Sets.union(union, anotherSet);}, since iterating over the resulting + * set has a cubic complexity to the depth of the nesting. + */ + public static SetView union( + final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + final Set set2minus1 = difference(set2, set1); + + return new SetView() { + @Override public int size() { + return set1.size() + set2minus1.size(); + } + @Override public boolean isEmpty() { + return set1.isEmpty() && set2.isEmpty(); + } + @Override public Iterator iterator() { + return Iterators.unmodifiableIterator( + Iterators.concat(set1.iterator(), set2minus1.iterator())); + } + @Override public boolean contains(Object object) { + return set1.contains(object) || set2.contains(object); + } + @Override public > S copyInto(S set) { + set.addAll(set1); + set.addAll(set2); + return set; + } + @Override public ImmutableSet immutableCopy() { + return new ImmutableSet.Builder() + .addAll(set1).addAll(set2).build(); + } + }; + } + + /** + * Returns an unmodifiable view of the intersection of two sets. The + * returned set contains all elements that are contained by both backing sets. + * The iteration order of the returned set matches that of {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based + * on different equivalence relations (as {@code HashSet}, {@code TreeSet}, + * and the keySet of an {@code IdentityHashMap} all are). + * + *

Note: The returned view performs slightly better when {@code + * set1} is the smaller of the two sets. If you have reason to believe one of + * your sets will generally be smaller than the other, pass it first. + * Unfortunately, since this method sets the generic type of the returned set + * based on the type of the first set passed, this could in rare cases force + * you to make a cast, for example:

   {@code
+   *
+   *   Set aFewBadObjects = ...
+   *   Set manyBadStrings = ...
+   *
+   *   // impossible for a non-String to be in the intersection
+   *   SuppressWarnings("unchecked")
+   *   Set badStrings = (Set) Sets.intersection(
+   *       aFewBadObjects, manyBadStrings);}
+   *
+   * This is unfortunate, but should come up only very rarely.
+   */
+  public static  SetView intersection(
+      final Set set1, final Set set2) {
+    checkNotNull(set1, "set1");
+    checkNotNull(set2, "set2");
+
+    final Predicate inSet2 = Predicates.in(set2);
+    return new SetView() {
+      @Override public Iterator iterator() {
+        return Iterators.filter(set1.iterator(), inSet2);
+      }
+      @Override public int size() {
+        return Iterators.size(iterator());
+      }
+      @Override public boolean isEmpty() {
+        return !iterator().hasNext();
+      }
+      @Override public boolean contains(Object object) {
+        return set1.contains(object) && set2.contains(object);
+      }
+      @Override public boolean containsAll(Collection collection) {
+        return set1.containsAll(collection)
+            && set2.containsAll(collection);
+      }
+    };
+  }
+
+  /**
+   * Returns an unmodifiable view of the difference of two sets. The
+   * returned set contains all elements that are contained by {@code set1} and
+   * not contained by {@code set2}. {@code set2} may also contain elements not
+   * present in {@code set1}; these are simply ignored. The iteration order of
+   * the returned set matches that of {@code set1}.
+   *
+   * 

Results are undefined if {@code set1} and {@code set2} are sets based + * on different equivalence relations (as {@code HashSet}, {@code TreeSet}, + * and the keySet of an {@code IdentityHashMap} all are). + */ + public static SetView difference( + final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + final Predicate notInSet2 = Predicates.not(Predicates.in(set2)); + return new SetView() { + @Override public Iterator iterator() { + return Iterators.filter(set1.iterator(), notInSet2); + } + @Override public int size() { + return Iterators.size(iterator()); + } + @Override public boolean isEmpty() { + return set2.containsAll(set1); + } + @Override public boolean contains(Object element) { + return set1.contains(element) && !set2.contains(element); + } + }; + } + + /** + * Returns an unmodifiable view of the symmetric difference of two + * sets. The returned set contains all elements that are contained in either + * {@code set1} or {@code set2} but not in both. The iteration order of the + * returned set is undefined. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based + * on different equivalence relations (as {@code HashSet}, {@code TreeSet}, + * and the keySet of an {@code IdentityHashMap} all are). + * + * @since 3.0 + */ + public static SetView symmetricDifference( + Set set1, Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + // TODO(kevinb): Replace this with a more efficient implementation + return difference(union(set1, set2), intersection(set1, set2)); + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * returned set is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all + * other set methods are supported. When given an element that doesn't satisfy + * the predicate, the set's {@code add()} and {@code addAll()} methods throw + * an {@link IllegalArgumentException}. When methods such as {@code + * removeAll()} and {@code clear()} are called on the filtered set, only + * elements that satisfy the filter will be removed from the underlying set. + * + *

The returned set isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate + * across every element in the underlying set and determine which elements + * satisfy the filter. When a live view is not needed, it may be faster + * to copy {@code Iterables.filter(unfiltered, predicate)} and use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, + * as documented at {@link Predicate#apply}. Do not provide a predicate such + * as {@code Predicates.instanceOf(ArrayList.class)}, which is inconsistent + * with equals. (See {@link Iterables#filter(Iterable, Class)} for related + * functionality.) + */ + // TODO(kevinb): how to omit that last sentence when building GWT javadoc? + public static Set filter( + Set unfiltered, Predicate predicate) { + if (unfiltered instanceof SortedSet) { + return filter((SortedSet) unfiltered, predicate); + } + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate + = Predicates.and(filtered.predicate, predicate); + return new FilteredSet( + (Set) filtered.unfiltered, combinedPredicate); + } + + return new FilteredSet( + checkNotNull(unfiltered), checkNotNull(predicate)); + } + + private static class FilteredSet extends FilteredCollection + implements Set { + FilteredSet(Set unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + @Override public boolean equals(@Nullable Object object) { + return equalsImpl(this, object); + } + + @Override public int hashCode() { + return hashCodeImpl(this); + } + } + + /** + * Returns the elements of a {@code SortedSet}, {@code unfiltered}, that + * satisfy a predicate. The returned set is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all + * other set methods are supported. When given an element that doesn't satisfy + * the predicate, the set's {@code add()} and {@code addAll()} methods throw + * an {@link IllegalArgumentException}. When methods such as + * {@code removeAll()} and {@code clear()} are called on the filtered set, + * only elements that satisfy the filter will be removed from the underlying + * set. + * + *

The returned set isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate across + * every element in the underlying set and determine which elements satisfy + * the filter. When a live view is not needed, it may be faster to copy + * {@code Iterables.filter(unfiltered, predicate)} and use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, + * as documented at {@link Predicate#apply}. Do not provide a predicate such as + * {@code Predicates.instanceOf(ArrayList.class)}, which is inconsistent with + * equals. (See {@link Iterables#filter(Iterable, Class)} for related + * functionality.) + * + * @since 11.0 + */ + @SuppressWarnings("unchecked") + public static SortedSet filter( + SortedSet unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate + = Predicates.and(filtered.predicate, predicate); + return new FilteredSortedSet( + (SortedSet) filtered.unfiltered, combinedPredicate); + } + + return new FilteredSortedSet( + checkNotNull(unfiltered), checkNotNull(predicate)); + } + + private static class FilteredSortedSet extends FilteredCollection + implements SortedSet { + + FilteredSortedSet(SortedSet unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + @Override public boolean equals(@Nullable Object object) { + return equalsImpl(this, object); + } + + @Override public int hashCode() { + return hashCodeImpl(this); + } + + @Override + public Comparator comparator() { + return ((SortedSet) unfiltered).comparator(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return new FilteredSortedSet(((SortedSet) unfiltered).subSet(fromElement, toElement), + predicate); + } + + @Override + public SortedSet headSet(E toElement) { + return new FilteredSortedSet(((SortedSet) unfiltered).headSet(toElement), predicate); + } + + @Override + public SortedSet tailSet(E fromElement) { + return new FilteredSortedSet(((SortedSet) unfiltered).tailSet(fromElement), predicate); + } + + @Override + public E first() { + return iterator().next(); + } + + @Override + public E last() { + SortedSet sortedUnfiltered = (SortedSet) unfiltered; + while (true) { + E element = sortedUnfiltered.last(); + if (predicate.apply(element)) { + return element; + } + sortedUnfiltered = sortedUnfiltered.headSet(element); + } + } + } + + /** + * Returns every possible list that can be formed by choosing one element + * from each of the given sets in order; the "n-ary + * Cartesian + * product" of the sets. For example:

   {@code
+   *
+   *   Sets.cartesianProduct(ImmutableList.of(
+   *       ImmutableSet.of(1, 2),
+   *       ImmutableSet.of("A", "B", "C")))}
+ * + * returns a set containing six lists: + * + *
    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + * The order in which these lists are returned is not guaranteed, however the + * position of an element inside a tuple always corresponds to the position of + * the set from which it came in the input list. Note that if any input set is + * empty, the Cartesian product will also be empty. If no sets at all are + * provided (an empty list), the resulting Cartesian product has one element, + * an empty list (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of sets of size + * {@code m, n, p} is a set of size {@code m x n x p}, its actual memory + * consumption is much smaller. When the cartesian set is constructed, the + * input sets are merely copied. Only as the resulting set is iterated are the + * individual lists created, and these are not retained after iteration. + * + * @param sets the sets to choose elements from, in the order that + * the elements chosen from those sets should appear in the resulting + * lists + * @param any common base class shared by all axes (often just {@link + * Object}) + * @return the Cartesian product, as an immutable set containing immutable + * lists + * @throws NullPointerException if {@code sets}, any one of the {@code sets}, + * or any element of a provided set is null + * @since 2.0 + */ + public static Set> cartesianProduct( + List> sets) { + for (Set set : sets) { + if (set.isEmpty()) { + return ImmutableSet.of(); + } + } + CartesianSet cartesianSet = new CartesianSet(sets); + return cartesianSet; + } + + /** + * Returns every possible list that can be formed by choosing one element + * from each of the given sets in order; the "n-ary + * Cartesian + * product" of the sets. For example:

   {@code
+   *
+   *   Sets.cartesianProduct(
+   *       ImmutableSet.of(1, 2),
+   *       ImmutableSet.of("A", "B", "C"))}
+ * + * returns a set containing six lists: + * + *
    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + * The order in which these lists are returned is not guaranteed, however the + * position of an element inside a tuple always corresponds to the position of + * the set from which it came in the input list. Note that if any input set is + * empty, the Cartesian product will also be empty. If no sets at all are + * provided, the resulting Cartesian product has one element, an empty list + * (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of sets of size + * {@code m, n, p} is a set of size {@code m x n x p}, its actual memory + * consumption is much smaller. When the cartesian set is constructed, the + * input sets are merely copied. Only as the resulting set is iterated are the + * individual lists created, and these are not retained after iteration. + * + * @param sets the sets to choose elements from, in the order that + * the elements chosen from those sets should appear in the resulting + * lists + * @param any common base class shared by all axes (often just {@link + * Object}) + * @return the Cartesian product, as an immutable set containing immutable + * lists + * @throws NullPointerException if {@code sets}, any one of the {@code sets}, + * or any element of a provided set is null + * @since 2.0 + */ + public static Set> cartesianProduct( + Set... sets) { + return cartesianProduct(Arrays.asList(sets)); + } + + private static class CartesianSet extends AbstractSet> { + final ImmutableList axes; + final int size; + + CartesianSet(List> sets) { + int dividend = 1; + ImmutableList.Builder builder = ImmutableList.builder(); + try { + for (Set set : sets) { + Axis axis = new Axis(set, dividend); + builder.add(axis); + dividend = IntMath.checkedMultiply(dividend, axis.size()); + } + } catch (ArithmeticException overflow) { + throw new IllegalArgumentException("cartesian product too big"); + } + this.axes = builder.build(); + size = dividend; + } + + @Override public int size() { + return size; + } + + @Override public UnmodifiableIterator> iterator() { + return new AbstractIndexedListIterator>(size) { + @Override + protected List get(int index) { + Object[] tuple = new Object[axes.size()]; + for (int i = 0 ; i < tuple.length; i++) { + tuple[i] = axes.get(i).getForIndex(index); + } + + @SuppressWarnings("unchecked") // only B's are put in here + List result = (ImmutableList) ImmutableList.copyOf(tuple); + return result; + } + }; + } + + @Override public boolean contains(Object element) { + if (!(element instanceof List)) { + return false; + } + List tuple = (List) element; + int dimensions = axes.size(); + if (tuple.size() != dimensions) { + return false; + } + for (int i = 0; i < dimensions; i++) { + if (!axes.get(i).contains(tuple.get(i))) { + return false; + } + } + return true; + } + + @Override public boolean equals(@Nullable Object object) { + // Warning: this is broken if size() == 0, so it is critical that we + // substitute an empty ImmutableSet to the user in place of this + if (object instanceof CartesianSet) { + CartesianSet that = (CartesianSet) object; + return this.axes.equals(that.axes); + } + return super.equals(object); + } + + @Override public int hashCode() { + // Warning: this is broken if size() == 0, so it is critical that we + // substitute an empty ImmutableSet to the user in place of this + + // It's a weird formula, but tests prove it works. + int adjust = size - 1; + for (int i = 0; i < axes.size(); i++) { + adjust *= 31; + } + return axes.hashCode() + adjust; + } + + private class Axis { + final ImmutableSet choices; + final ImmutableList choicesList; + final int dividend; + + Axis(Set set, int dividend) { + choices = ImmutableSet.copyOf(set); + choicesList = choices.asList(); + this.dividend = dividend; + } + + int size() { + return choices.size(); + } + + B getForIndex(int index) { + return choicesList.get(index / dividend % size()); + } + + boolean contains(Object target) { + return choices.contains(target); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof CartesianSet.Axis) { + CartesianSet.Axis that = (CartesianSet.Axis) obj; + return this.choices.equals(that.choices); + // dividends must be equal or we wouldn't have gotten this far + } + return false; + } + + @Override public int hashCode() { + // Because Axis instances are not exposed, we can + // opportunistically choose whatever bizarre formula happens + // to make CartesianSet.hashCode() as simple as possible. + return size / choices.size() * choices.hashCode(); + } + } + } + + /** + * Returns the set of all possible subsets of {@code set}. For example, + * {@code powerSet(ImmutableSet.of(1, 2))} returns the set {@code {{}, + * {1}, {2}, {1, 2}}}. + * + *

Elements appear in these subsets in the same iteration order as they + * appeared in the input set. The order in which these subsets appear in the + * outer set is undefined. Note that the power set of the empty set is not the + * empty set, but a one-element set containing the empty set. + * + *

The returned set and its constituent sets use {@code equals} to decide + * whether two elements are identical, even if the input set uses a different + * concept of equivalence. + * + *

Performance notes: while the power set of a set with size {@code + * n} is of size {@code 2^n}, its memory usage is only {@code O(n)}. When the + * power set is constructed, the input set is merely copied. Only as the + * power set is iterated are the individual subsets created, and these subsets + * themselves occupy only a few bytes of memory regardless of their size. + * + * @param set the set of elements to construct a power set from + * @return the power set, as an immutable set of immutable sets + * @throws IllegalArgumentException if {@code set} has more than 30 unique + * elements (causing the power set size to exceed the {@code int} range) + * @throws NullPointerException if {@code set} is or contains {@code null} + * @see Power set article at + * Wikipedia + * @since 4.0 + */ + @GwtCompatible(serializable = false) + public static Set> powerSet(Set set) { + ImmutableSet input = ImmutableSet.copyOf(set); + checkArgument(input.size() <= 30, + "Too many elements to create power set: %s > 30", input.size()); + return new PowerSet(input); + } + + private static final class PowerSet extends AbstractSet> { + final ImmutableSet inputSet; + final ImmutableList inputList; + final int powerSetSize; + + PowerSet(ImmutableSet input) { + this.inputSet = input; + this.inputList = input.asList(); + this.powerSetSize = 1 << input.size(); + } + + @Override public int size() { + return powerSetSize; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Iterator> iterator() { + return new AbstractIndexedListIterator>(powerSetSize) { + @Override protected Set get(final int setBits) { + return new AbstractSet() { + @Override public int size() { + return Integer.bitCount(setBits); + } + @Override public Iterator iterator() { + return new BitFilteredSetIterator(inputList, setBits); + } + }; + } + }; + } + + private static final class BitFilteredSetIterator + extends UnmodifiableIterator { + final ImmutableList input; + int remainingSetBits; + + BitFilteredSetIterator(ImmutableList input, int allSetBits) { + this.input = input; + this.remainingSetBits = allSetBits; + } + + @Override public boolean hasNext() { + return remainingSetBits != 0; + } + + @Override public E next() { + int index = Integer.numberOfTrailingZeros(remainingSetBits); + if (index == 32) { + throw new NoSuchElementException(); + } + + int currentElementMask = 1 << index; + remainingSetBits &= ~currentElementMask; + return input.get(index); + } + } + + @Override public boolean contains(@Nullable Object obj) { + if (obj instanceof Set) { + Set set = (Set) obj; + return inputSet.containsAll(set); + } + return false; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof PowerSet) { + PowerSet that = (PowerSet) obj; + return inputSet.equals(that.inputSet); + } + return super.equals(obj); + } + + @Override public int hashCode() { + /* + * The sum of the sums of the hash codes in each subset is just the sum of + * each input element's hash code times the number of sets that element + * appears in. Each element appears in exactly half of the 2^n sets, so: + */ + return inputSet.hashCode() << (inputSet.size() - 1); + } + + @Override public String toString() { + return "powerSet(" + inputSet + ")"; + } + } + + /** + * An implementation for {@link Set#hashCode()}. + */ + static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + } + return hashCode; + } + + /** + * An implementation for {@link Set#equals(Object)}. + */ + static boolean equalsImpl(Set s, @Nullable Object object){ + if (s == object) { + return true; + } + if (object instanceof Set) { + Set o = (Set) object; + + try { + return s.size() == o.size() && s.containsAll(o); + } catch (NullPointerException ignored) { + return false; + } catch (ClassCastException ignored) { + return false; + } + } + return false; + } + + /** + * Returns an unmodifiable view of the specified navigable set. This method + * allows modules to provide users with "read-only" access to internal + * navigable sets. Query operations on the returned set "read through" to the + * specified set, and attempts to modify the returned set, whether direct or + * via its collection views, result in an + * {@code UnsupportedOperationException}. + * + *

The returned navigable set will be serializable if the specified + * navigable set is serializable. + * + * @param set the navigable set for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified navigable set + * @since 12.0 + */ + @GwtIncompatible("NavigableSet") + public static NavigableSet unmodifiableNavigableSet( + NavigableSet set) { + if (set instanceof ImmutableSortedSet + || set instanceof UnmodifiableNavigableSet) { + return set; + } + return new UnmodifiableNavigableSet(set); + } + + @GwtIncompatible("NavigableSet") + static final class UnmodifiableNavigableSet + extends ForwardingSortedSet implements NavigableSet, Serializable { + private final NavigableSet delegate; + + UnmodifiableNavigableSet(NavigableSet delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + protected SortedSet delegate() { + return Collections.unmodifiableSortedSet(delegate); + } + + @Override + public E lower(E e) { + return delegate.lower(e); + } + + @Override + public E floor(E e) { + return delegate.floor(e); + } + + @Override + public E ceiling(E e) { + return delegate.ceiling(e); + } + + @Override + public E higher(E e) { + return delegate.higher(e); + } + + @Override + public E pollFirst() { + throw new UnsupportedOperationException(); + } + + @Override + public E pollLast() { + throw new UnsupportedOperationException(); + } + + private transient UnmodifiableNavigableSet descendingSet; + + @Override + public NavigableSet descendingSet() { + UnmodifiableNavigableSet result = descendingSet; + if (result == null) { + result = descendingSet = new UnmodifiableNavigableSet( + delegate.descendingSet()); + result.descendingSet = this; + } + return result; + } + + @Override + public Iterator descendingIterator() { + return Iterators.unmodifiableIterator(delegate.descendingIterator()); + } + + @Override + public NavigableSet subSet( + E fromElement, + boolean fromInclusive, + E toElement, + boolean toInclusive) { + return unmodifiableNavigableSet(delegate.subSet( + fromElement, + fromInclusive, + toElement, + toInclusive)); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return unmodifiableNavigableSet(delegate.headSet(toElement, inclusive)); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return unmodifiableNavigableSet( + delegate.tailSet(fromElement, inclusive)); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) navigable set backed by the specified + * navigable set. In order to guarantee serial access, it is critical that + * all access to the backing navigable set is accomplished + * through the returned navigable set (or its views). + * + *

It is imperative that the user manually synchronize on the returned + * sorted set when iterating over it or any of its {@code descendingSet}, + * {@code subSet}, {@code headSet}, or {@code tailSet} views.

   {@code
+   *
+   *   NavigableSet set = synchronizedNavigableSet(new TreeSet());
+   *    ...
+   *   synchronized (set) {
+   *     // Must be in the synchronized block
+   *     Iterator it = set.iterator();
+   *     while (it.hasNext()){
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * or:
   {@code
+   *
+   *   NavigableSet set = synchronizedNavigableSet(new TreeSet());
+   *   NavigableSet set2 = set.descendingSet().headSet(foo);
+   *    ...
+   *   synchronized (set) { // Note: set, not set2!!!
+   *     // Must be in the synchronized block
+   *     Iterator it = set2.descendingIterator();
+   *     while (it.hasNext())
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned navigable set will be serializable if the specified + * navigable set is serializable. + * + * @param navigableSet the navigable set to be "wrapped" in a synchronized + * navigable set. + * @return a synchronized view of the specified navigable set. + * @since 13.0 + */ + @Beta + @GwtIncompatible("NavigableSet") + public static NavigableSet synchronizedNavigableSet( + NavigableSet navigableSet) { + return Synchronized.navigableSet(navigableSet); + } + + /** + * Remove each element in an iterable from a set. + */ + static boolean removeAllImpl(Set set, Iterator iterator) { + boolean changed = false; + while (iterator.hasNext()) { + changed |= set.remove(iterator.next()); + } + return changed; + } + + static boolean removeAllImpl(Set set, Collection collection) { + checkNotNull(collection); // for GWT + if (collection instanceof Multiset) { + collection = ((Multiset) collection).elementSet(); + } + /* + * AbstractSet.removeAll(List) has quadratic behavior if the list size + * is just less than the set's size. We augment the test by + * assuming that sets have fast contains() performance, and other + * collections don't. See + * http://code.google.com/p/guava-libraries/issues/detail?id=1013 + */ + if (collection instanceof Set && collection.size() > set.size()) { + Iterator setIterator = set.iterator(); + boolean changed = false; + while (setIterator.hasNext()) { + if (collection.contains(setIterator.next())) { + changed = true; + setIterator.remove(); + } + } + return changed; + } else { + return removeAllImpl(set, collection.iterator()); + } + } + + @GwtIncompatible("NavigableSet") + static class DescendingSet extends ForwardingNavigableSet { + private final NavigableSet forward; + + DescendingSet(NavigableSet forward) { + this.forward = forward; + } + + @Override + protected NavigableSet delegate() { + return forward; + } + + @Override + public E lower(E e) { + return forward.higher(e); + } + + @Override + public E floor(E e) { + return forward.ceiling(e); + } + + @Override + public E ceiling(E e) { + return forward.floor(e); + } + + @Override + public E higher(E e) { + return forward.lower(e); + } + + @Override + public E pollFirst() { + return forward.pollLast(); + } + + @Override + public E pollLast() { + return forward.pollFirst(); + } + + @Override + public NavigableSet descendingSet() { + return forward; + } + + @Override + public Iterator descendingIterator() { + return forward.iterator(); + } + + @Override + public NavigableSet subSet( + E fromElement, + boolean fromInclusive, + E toElement, + boolean toInclusive) { + return forward.subSet(toElement, toInclusive, fromElement, fromInclusive).descendingSet(); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return forward.tailSet(toElement, inclusive).descendingSet(); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return forward.headSet(fromElement, inclusive).descendingSet(); + } + + @SuppressWarnings("unchecked") + @Override + public Comparator comparator() { + Comparator forwardComparator = forward.comparator(); + if (forwardComparator == null) { + return (Comparator) Ordering.natural().reverse(); + } else { + return reverse(forwardComparator); + } + } + + // If we inline this, we get a javac error. + private static Ordering reverse(Comparator forward) { + return Ordering.from(forward).reverse(); + } + + @Override + public E first() { + return forward.last(); + } + + @Override + public SortedSet headSet(E toElement) { + return standardHeadSet(toElement); + } + + @Override + public E last() { + return forward.first(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return standardSubSet(fromElement, toElement); + } + + @Override + public SortedSet tailSet(E fromElement) { + return standardTailSet(fromElement); + } + + @Override + public Iterator iterator() { + return forward.descendingIterator(); + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override + public String toString() { + return standardToString(); + } + } + + /** + * Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 + */ + static SortedSet cast(Iterable iterable) { + return (SortedSet) iterable; + } +} diff --git a/guava/src/com/google/common/collect/SingletonImmutableList.java b/guava/src/com/google/common/collect/SingletonImmutableList.java new file mode 100644 index 0000000..07659a0 --- /dev/null +++ b/guava/src/com/google/common/collect/SingletonImmutableList.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableList} with exactly one element. + * + * @author Hayward Chan + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableList extends ImmutableList { + + final transient E element; + + SingletonImmutableList(E element) { + this.element = checkNotNull(element); + } + + @Override + public E get(int index) { + Preconditions.checkElementIndex(index, 1); + return element; + } + + @Override public int indexOf(@Nullable Object object) { + return element.equals(object) ? 0 : -1; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override public int lastIndexOf(@Nullable Object object) { + return indexOf(object); + } + + @Override + public int size() { + return 1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + Preconditions.checkPositionIndexes(fromIndex, toIndex, 1); + return (fromIndex == toIndex) ? ImmutableList.of() : this; + } + + @Override public ImmutableList reverse() { + return this; + } + + @Override public boolean contains(@Nullable Object object) { + return element.equals(object); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof List) { + List that = (List) object; + return that.size() == 1 && element.equals(that.get(0)); + } + return false; + } + + @Override public int hashCode() { + // not caching hash code since it could change if the element is mutable + // in a way that modifies its hash code. + return 31 + element.hashCode(); + } + + @Override public String toString() { + String elementToString = element.toString(); + return new StringBuilder(elementToString.length() + 2) + .append('[') + .append(elementToString) + .append(']') + .toString(); + } + + @Override public boolean isEmpty() { + return false; + } + + @Override boolean isPartialView() { + return false; + } + + @Override public Object[] toArray() { + return new Object[] { element }; + } + + @Override public T[] toArray(T[] array) { + if (array.length == 0) { + array = ObjectArrays.newArray(array, 1); + } else if (array.length > 1) { + array[1] = null; + } + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] objectArray = array; + objectArray[0] = element; + return array; + } +} diff --git a/guava/src/com/google/common/collect/SingletonImmutableMap.java b/guava/src/com/google/common/collect/SingletonImmutableMap.java new file mode 100644 index 0000000..7345a95 --- /dev/null +++ b/guava/src/com/google/common/collect/SingletonImmutableMap.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableMap} with exactly one entry. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableMap extends ImmutableMap { + + final transient K singleKey; + final transient V singleValue; + + SingletonImmutableMap(K singleKey, V singleValue) { + this.singleKey = singleKey; + this.singleValue = singleValue; + } + + SingletonImmutableMap(Entry entry) { + this(entry.getKey(), entry.getValue()); + } + + @Override public V get(@Nullable Object key) { + return singleKey.equals(key) ? singleValue : null; + } + + @Override + public int size() { + return 1; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean containsKey(@Nullable Object key) { + return singleKey.equals(key); + } + + @Override public boolean containsValue(@Nullable Object value) { + return singleValue.equals(value); + } + + @Override boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return ImmutableSet.of(Maps.immutableEntry(singleKey, singleValue)); + } + + @Override + ImmutableSet createKeySet() { + return ImmutableSet.of(singleKey); + } + + @Override + ImmutableCollection createValues() { + return ImmutableList.of(singleValue); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Map) { + Map that = (Map) object; + if (that.size() == 1) { + Entry entry = that.entrySet().iterator().next(); + return singleKey.equals(entry.getKey()) + && singleValue.equals(entry.getValue()); + } + } + return false; + } + + @Override public int hashCode() { + return singleKey.hashCode() ^ singleValue.hashCode(); + } +} diff --git a/guava/src/com/google/common/collect/SingletonImmutableSet.java b/guava/src/com/google/common/collect/SingletonImmutableSet.java new file mode 100644 index 0000000..f64aac1 --- /dev/null +++ b/guava/src/com/google/common/collect/SingletonImmutableSet.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableSet} with exactly one element. + * + * @author Kevin Bourrillion + * @author Nick Kralevich + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableSet extends ImmutableSet { + + final transient E element; + // This is transient because it will be recalculated on the first + // call to hashCode(). + // + // A race condition is avoided since threads will either see that the value + // is zero and recalculate it themselves, or two threads will see it at + // the same time, and both recalculate it. If the cachedHashCode is 0, + // it will always be recalculated, unfortunately. + private transient int cachedHashCode; + + SingletonImmutableSet(E element) { + this.element = Preconditions.checkNotNull(element); + } + + SingletonImmutableSet(E element, int hashCode) { + // Guaranteed to be non-null by the presence of the pre-computed hash code. + this.element = element; + cachedHashCode = hashCode; + } + + @Override + public int size() { + return 1; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean contains(Object target) { + return element.equals(target); + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override boolean isPartialView() { + return false; + } + + @Override public Object[] toArray() { + return new Object[] { element }; + } + + @Override public T[] toArray(T[] array) { + if (array.length == 0) { + array = ObjectArrays.newArray(array, 1); + } else if (array.length > 1) { + array[1] = null; + } + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] objectArray = array; + objectArray[0] = element; + return array; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Set) { + Set that = (Set) object; + return that.size() == 1 && element.equals(that.iterator().next()); + } + return false; + } + + @Override public final int hashCode() { + // Racy single-check. + int code = cachedHashCode; + if (code == 0) { + cachedHashCode = code = element.hashCode(); + } + return code; + } + + @Override boolean isHashCodeFast() { + return cachedHashCode != 0; + } + + @Override public String toString() { + String elementToString = element.toString(); + return new StringBuilder(elementToString.length() + 2) + .append('[') + .append(elementToString) + .append(']') + .toString(); + } +} diff --git a/guava/src/com/google/common/collect/SingletonImmutableTable.java b/guava/src/com/google/common/collect/SingletonImmutableTable.java new file mode 100644 index 0000000..e6e850c --- /dev/null +++ b/guava/src/com/google/common/collect/SingletonImmutableTable.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An implementation of {@link ImmutableTable} that holds a single cell. + * + * @author Gregory Kick + */ +@GwtCompatible +final class SingletonImmutableTable extends ImmutableTable { + private final R singleRowKey; + private final C singleColumnKey; + private final V singleValue; + + SingletonImmutableTable(R rowKey, C columnKey, V value) { + this.singleRowKey = checkNotNull(rowKey); + this.singleColumnKey = checkNotNull(columnKey); + this.singleValue = checkNotNull(value); + } + + SingletonImmutableTable(Cell cell) { + this(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + + @Override public ImmutableSet> cellSet() { + return ImmutableSet.of( + Tables.immutableCell(singleRowKey, singleColumnKey, singleValue)); + } + + @Override public ImmutableMap column(C columnKey) { + checkNotNull(columnKey); + return containsColumn(columnKey) + ? ImmutableMap.of(singleRowKey, singleValue) + : ImmutableMap.of(); + } + + @Override public ImmutableSet columnKeySet() { + return ImmutableSet.of(singleColumnKey); + } + + @Override public ImmutableMap> columnMap() { + return ImmutableMap.of(singleColumnKey, + (Map) ImmutableMap.of(singleRowKey, singleValue)); + } + + @Override public boolean contains(@Nullable Object rowKey, + @Nullable Object columnKey) { + return containsRow(rowKey) && containsColumn(columnKey); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return Objects.equal(this.singleColumnKey, columnKey); + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return Objects.equal(this.singleRowKey, rowKey); + } + + @Override public boolean containsValue(@Nullable Object value) { + return Objects.equal(this.singleValue, value); + } + + @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + return contains(rowKey, columnKey) ? singleValue : null; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public ImmutableMap row(R rowKey) { + checkNotNull(rowKey); + return containsRow(rowKey) + ? ImmutableMap.of(singleColumnKey, singleValue) + : ImmutableMap.of(); + } + + @Override public ImmutableSet rowKeySet() { + return ImmutableSet.of(singleRowKey); + } + + @Override public ImmutableMap> rowMap() { + return ImmutableMap.of(singleRowKey, + (Map) ImmutableMap.of(singleColumnKey, singleValue)); + } + + @Override public int size() { + return 1; + } + + @Override public ImmutableCollection values() { + return ImmutableSet.of(singleValue); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + Table that = (Table) obj; + if (that.size() == 1) { + Cell thatCell = that.cellSet().iterator().next(); + return Objects.equal(this.singleRowKey, thatCell.getRowKey()) && + Objects.equal(this.singleColumnKey, thatCell.getColumnKey()) && + Objects.equal(this.singleValue, thatCell.getValue()); + } + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(singleRowKey, singleColumnKey, singleValue); + } + + @Override public String toString() { + return new StringBuilder() + .append('{') + .append(singleRowKey) + .append("={") + .append(singleColumnKey) + .append('=') + .append(singleValue) + .append("}}") + .toString(); + } +} diff --git a/guava/src/com/google/common/collect/SortedIterable.java b/guava/src/com/google/common/collect/SortedIterable.java new file mode 100644 index 0000000..e859114 --- /dev/null +++ b/guava/src/com/google/common/collect/SortedIterable.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * An {@code Iterable} whose elements are sorted relative to a {@code Comparator}, typically + * provided at creation time. + * + * @author Louis Wasserman + */ +@GwtCompatible +interface SortedIterable extends Iterable { + /** + * Returns the {@code Comparator} by which the elements of this iterable are ordered, or {@code + * Ordering.natural()} if the elements are ordered by their natural ordering. + */ + Comparator comparator(); + + /** + * Returns an iterator over elements of type {@code T}. The elements are returned in + * nondecreasing order according to the associated {@link #comparator}. + */ + @Override + Iterator iterator(); +} diff --git a/guava/src/com/google/common/collect/SortedIterables.java b/guava/src/com/google/common/collect/SortedIterables.java new file mode 100644 index 0000000..2158e9f --- /dev/null +++ b/guava/src/com/google/common/collect/SortedIterables.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.SortedSet; + +/** + * Utilities for dealing with sorted collections of all types. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class SortedIterables { + private SortedIterables() {} + + /** + * Returns {@code true} if {@code elements} is a sorted collection using an ordering equivalent + * to {@code comparator}. + */ + public static boolean hasSameComparator(Comparator comparator, Iterable elements) { + checkNotNull(comparator); + checkNotNull(elements); + Comparator comparator2; + if (elements instanceof SortedSet) { + comparator2 = comparator((SortedSet) elements); + } else if (elements instanceof SortedIterable) { + comparator2 = ((SortedIterable) elements).comparator(); + } else { + return false; + } + return comparator.equals(comparator2); + } + + @SuppressWarnings("unchecked") + // if sortedSet.comparator() is null, the set must be naturally ordered + public static Comparator comparator(SortedSet sortedSet) { + Comparator result = sortedSet.comparator(); + if (result == null) { + result = (Comparator) Ordering.natural(); + } + return result; + } +} diff --git a/guava/src/com/google/common/collect/SortedLists.java b/guava/src/com/google/common/collect/SortedLists.java new file mode 100644 index 0000000..6fc0d71 --- /dev/null +++ b/guava/src/com/google/common/collect/SortedLists.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +import javax.annotation.Nullable; + +/** + * Static methods pertaining to sorted {@link List} instances. + * + * In this documentation, the terms greatest, greater, least, and + * lesser are considered to refer to the comparator on the elements, and the terms + * first and last are considered to refer to the elements' ordering in a + * list. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Beta final class SortedLists { + private SortedLists() {} + + /** + * A specification for which index to return if the list contains at least one element that + * compares as equal to the key. + */ + public enum KeyPresentBehavior { + /** + * Return the index of any list element that compares as equal to the key. No guarantees are + * made as to which index is returned, if more than one element compares as equal to the key. + */ + ANY_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return foundIndex; + } + }, + /** + * Return the index of the last list element that compares as equal to the key. + */ + LAST_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + // Of course, we have to use binary search to find the precise + // breakpoint... + int lower = foundIndex; + int upper = list.size() - 1; + // Everything between lower and upper inclusive compares at >= 0. + while (lower < upper) { + int middle = (lower + upper + 1) >>> 1; + int c = comparator.compare(list.get(middle), key); + if (c > 0) { + upper = middle - 1; + } else { // c == 0 + lower = middle; + } + } + return lower; + } + }, + /** + * Return the index of the first list element that compares as equal to the key. + */ + FIRST_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + // Of course, we have to use binary search to find the precise + // breakpoint... + int lower = 0; + int upper = foundIndex; + // Of course, we have to use binary search to find the precise breakpoint... + // Everything between lower and upper inclusive compares at <= 0. + while (lower < upper) { + int middle = (lower + upper) >>> 1; + int c = comparator.compare(list.get(middle), key); + if (c < 0) { + lower = middle + 1; + } else { // c == 0 + upper = middle; + } + } + return lower; + } + }, + /** + * Return the index of the first list element that compares as greater than the key, or {@code + * list.size()} if there is no such element. + */ + FIRST_AFTER { + @Override + public int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return LAST_PRESENT.resultIndex(comparator, key, list, foundIndex) + 1; + } + }, + /** + * Return the index of the last list element that compares as less than the key, or {@code -1} + * if there is no such element. + */ + LAST_BEFORE { + @Override + public int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return FIRST_PRESENT.resultIndex(comparator, key, list, foundIndex) - 1; + } + }; + abstract int resultIndex( + Comparator comparator, E key, List list, int foundIndex); + } + + /** + * A specification for which index to return if the list contains no elements that compare as + * equal to the key. + */ + public enum KeyAbsentBehavior { + /** + * Return the index of the next lower element in the list, or {@code -1} if there is no such + * element. + */ + NEXT_LOWER { + @Override + int resultIndex(int higherIndex) { + return higherIndex - 1; + } + }, + /** + * Return the index of the next higher element in the list, or {@code list.size()} if there is + * no such element. + */ + NEXT_HIGHER { + @Override + public int resultIndex(int higherIndex) { + return higherIndex; + } + }, + /** + * Return {@code ~insertionIndex}, where {@code insertionIndex} is defined as the point at + * which the key would be inserted into the list: the index of the next higher element in the + * list, or {@code list.size()} if there is no such element. + * + *

Note that the return value will be {@code >= 0} if and only if there is an element of the + * list that compares as equal to the key. + * + *

This is equivalent to the behavior of + * {@link java.util.Collections#binarySearch(List, Object)} when the key isn't present, since + * {@code ~insertionIndex} is equal to {@code -1 - insertionIndex}. + */ + INVERTED_INSERTION_INDEX { + @Override + public int resultIndex(int higherIndex) { + return ~higherIndex; + } + }; + + abstract int resultIndex(int higherIndex); + } + + /** + * Searches the specified naturally ordered list for the specified object using the binary search + * algorithm. + * + *

Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, + * KeyAbsentBehavior)} using {@link Ordering#natural}. + */ + public static int binarySearch(List list, E e, + KeyPresentBehavior presentBehavior, KeyAbsentBehavior absentBehavior) { + checkNotNull(e); + return binarySearch( + list, checkNotNull(e), Ordering.natural(), presentBehavior, absentBehavior); + } + + /** + * Binary searches the list for the specified key, using the specified key function. + * + *

Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, + * KeyAbsentBehavior)} using {@link Ordering#natural}. + */ + public static int binarySearch(List list, + Function keyFunction, @Nullable K key, KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + return binarySearch( + list, + keyFunction, + key, + Ordering.natural(), + presentBehavior, + absentBehavior); + } + + /** + * Binary searches the list for the specified key, using the specified key function. + * + *

Equivalent to + * {@link #binarySearch(List, Object, Comparator, KeyPresentBehavior, KeyAbsentBehavior)} using + * {@link Lists#transform(List, Function) Lists.transform(list, keyFunction)}. + */ + public static int binarySearch( + List list, + Function keyFunction, + @Nullable K key, + Comparator keyComparator, + KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + return binarySearch( + Lists.transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior); + } + + /** + * Searches the specified list for the specified object using the binary search algorithm. The + * list must be sorted into ascending order according to the specified comparator (as by the + * {@link Collections#sort(List, Comparator) Collections.sort(List, Comparator)} method), prior + * to making this call. If it is not sorted, the results are undefined. + * + *

If there are elements in the list which compare as equal to the key, the choice of + * {@link KeyPresentBehavior} decides which index is returned. If no elements compare as equal to + * the key, the choice of {@link KeyAbsentBehavior} decides which index is returned. + * + *

This method runs in log(n) time on random-access lists, which offer near-constant-time + * access to each list element. + * + * @param list the list to be searched. + * @param key the value to be searched for. + * @param comparator the comparator by which the list is ordered. + * @param presentBehavior the specification for what to do if at least one element of the list + * compares as equal to the key. + * @param absentBehavior the specification for what to do if no elements of the list compare as + * equal to the key. + * @return the index determined by the {@code KeyPresentBehavior}, if the key is in the list; + * otherwise the index determined by the {@code KeyAbsentBehavior}. + */ + public static int binarySearch(List list, @Nullable E key, + Comparator comparator, KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + checkNotNull(comparator); + checkNotNull(list); + checkNotNull(presentBehavior); + checkNotNull(absentBehavior); + if (!(list instanceof RandomAccess)) { + list = Lists.newArrayList(list); + } + // TODO(user): benchmark when it's best to do a linear search + + int lower = 0; + int upper = list.size() - 1; + + while (lower <= upper) { + int middle = (lower + upper) >>> 1; + int c = comparator.compare(key, list.get(middle)); + if (c < 0) { + upper = middle - 1; + } else if (c > 0) { + lower = middle + 1; + } else { + return lower + presentBehavior.resultIndex( + comparator, key, list.subList(lower, upper + 1), middle - lower); + } + } + return absentBehavior.resultIndex(lower); + } +} diff --git a/guava/src/com/google/common/collect/SortedMapDifference.java b/guava/src/com/google/common/collect/SortedMapDifference.java new file mode 100644 index 0000000..f44aa1a --- /dev/null +++ b/guava/src/com/google/common/collect/SortedMapDifference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.SortedMap; + +/** + * An object representing the differences between two sorted maps. + * + * @author Louis Wasserman + * @since 8.0 + */ +@Beta +@GwtCompatible +public interface SortedMapDifference extends MapDifference { + + @Override + SortedMap entriesOnlyOnLeft(); + + @Override + SortedMap entriesOnlyOnRight(); + + @Override + SortedMap entriesInCommon(); + + @Override + SortedMap> entriesDiffering(); +} diff --git a/guava/src/com/google/common/collect/SortedMultiset.java b/guava/src/com/google/common/collect/SortedMultiset.java new file mode 100644 index 0000000..5d5974a --- /dev/null +++ b/guava/src/com/google/common/collect/SortedMultiset.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.SortedSet; + +/** + * A {@link Multiset} which maintains the ordering of its elements, according to + * either their natural order or an explicit {@link Comparator}. In all cases, + * this implementation uses {@link Comparable#compareTo} or + * {@link Comparator#compare} instead of {@link Object#equals} to determine + * equivalence of instances. + * + *

Warning: The comparison must be consistent with equals as + * explained by the {@link Comparable} class specification. Otherwise, the + * resulting multiset will violate the {@link Collection} contract, which it is + * specified in terms of {@link Object#equals}. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible +public interface SortedMultiset extends Multiset, SortedIterable { + /** + * Returns the comparator that orders this multiset, or + * {@link Ordering#natural()} if the natural ordering of the elements is used. + */ + Comparator comparator(); + + /** + * Returns the entry of the first element in this multiset, or {@code null} if + * this multiset is empty. + */ + Entry firstEntry(); + + /** + * Returns the entry of the last element in this multiset, or {@code null} if + * this multiset is empty. + */ + Entry lastEntry(); + + /** + * Returns and removes the entry associated with the lowest element in this + * multiset, or returns {@code null} if this multiset is empty. + */ + Entry pollFirstEntry(); + + /** + * Returns and removes the entry associated with the greatest element in this + * multiset, or returns {@code null} if this multiset is empty. + */ + Entry pollLastEntry(); + + /** + * Returns a {@link SortedSet} view of the distinct elements in this multiset. + */ + @Override SortedSet elementSet(); + + /** + * {@inheritDoc} + * + *

The iterator returns the elements in ascending order according to this + * multiset's comparator. + */ + @Override Iterator iterator(); + + /** + * Returns a descending view of this multiset. Modifications made to either + * map will be reflected in the other. + */ + SortedMultiset descendingMultiset(); + + /** + * Returns a view of this multiset restricted to the elements less than + * {@code upperBound}, optionally including {@code upperBound} itself. The + * returned multiset is a view of this multiset, so changes to one will be + * reflected in the other. The returned multiset supports all operations that + * this multiset supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on + * attempts to add elements outside its range. + */ + SortedMultiset headMultiset(E upperBound, BoundType boundType); + + /** + * Returns a view of this multiset restricted to the range between + * {@code lowerBound} and {@code upperBound}. The returned multiset is a view + * of this multiset, so changes to one will be reflected in the other. The + * returned multiset supports all operations that this multiset supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on + * attempts to add elements outside its range. + * + *

This method is equivalent to + * {@code tailMultiset(lowerBound, lowerBoundType).headMultiset(upperBound, + * upperBoundType)}. + */ + SortedMultiset subMultiset(E lowerBound, BoundType lowerBoundType, + E upperBound, BoundType upperBoundType); + + /** + * Returns a view of this multiset restricted to the elements greater than + * {@code lowerBound}, optionally including {@code lowerBound} itself. The + * returned multiset is a view of this multiset, so changes to one will be + * reflected in the other. The returned multiset supports all operations that + * this multiset supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on + * attempts to add elements outside its range. + */ + SortedMultiset tailMultiset(E lowerBound, BoundType boundType); +} diff --git a/guava/src/com/google/common/collect/SortedMultisets.java b/guava/src/com/google/common/collect/SortedMultisets.java new file mode 100644 index 0000000..ff18b74 --- /dev/null +++ b/guava/src/com/google/common/collect/SortedMultisets.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.Multiset.Entry; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +/** + * Provides static utility methods for creating and working with + * {@link SortedMultiset} instances. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class SortedMultisets { + private SortedMultisets() { + } + + /** + * A skeleton implementation for {@link SortedMultiset#elementSet}. + */ + static abstract class ElementSet extends Multisets.ElementSet implements + SortedSet { + @Override abstract SortedMultiset multiset(); + + @Override public Comparator comparator() { + return multiset().comparator(); + } + + @Override public SortedSet subSet(E fromElement, E toElement) { + return multiset().subMultiset(fromElement, BoundType.CLOSED, toElement, + BoundType.OPEN).elementSet(); + } + + @Override public SortedSet headSet(E toElement) { + return multiset().headMultiset(toElement, BoundType.OPEN).elementSet(); + } + + @Override public SortedSet tailSet(E fromElement) { + return multiset().tailMultiset(fromElement, BoundType.CLOSED) + .elementSet(); + } + + @Override public E first() { + return getElementOrThrow(multiset().firstEntry()); + } + + @Override public E last() { + return getElementOrThrow(multiset().lastEntry()); + } + } + + private static E getElementOrThrow(Entry entry) { + if (entry == null) { + throw new NoSuchElementException(); + } + return entry.getElement(); + } + + /** + * A skeleton implementation of a descending multiset. Only needs + * {@code forwardMultiset()} and {@code entryIterator()}. + */ + static abstract class DescendingMultiset extends ForwardingMultiset + implements SortedMultiset { + abstract SortedMultiset forwardMultiset(); + + private transient Comparator comparator; + + @Override public Comparator comparator() { + Comparator result = comparator; + if (result == null) { + return comparator = + Ordering.from(forwardMultiset().comparator()).reverse(); + } + return result; + } + + private transient SortedSet elementSet; + + @Override public SortedSet elementSet() { + SortedSet result = elementSet; + if (result == null) { + return elementSet = new SortedMultisets.ElementSet() { + @Override SortedMultiset multiset() { + return DescendingMultiset.this; + } + }; + } + return result; + } + + @Override public Entry pollFirstEntry() { + return forwardMultiset().pollLastEntry(); + } + + @Override public Entry pollLastEntry() { + return forwardMultiset().pollFirstEntry(); + } + + @Override public SortedMultiset headMultiset(E toElement, + BoundType boundType) { + return forwardMultiset().tailMultiset(toElement, boundType) + .descendingMultiset(); + } + + @Override public SortedMultiset subMultiset(E fromElement, + BoundType fromBoundType, E toElement, BoundType toBoundType) { + return forwardMultiset().subMultiset(toElement, toBoundType, fromElement, + fromBoundType).descendingMultiset(); + } + + @Override public SortedMultiset tailMultiset(E fromElement, + BoundType boundType) { + return forwardMultiset().headMultiset(fromElement, boundType) + .descendingMultiset(); + } + + @Override protected Multiset delegate() { + return forwardMultiset(); + } + + @Override public SortedMultiset descendingMultiset() { + return forwardMultiset(); + } + + @Override public Entry firstEntry() { + return forwardMultiset().lastEntry(); + } + + @Override public Entry lastEntry() { + return forwardMultiset().firstEntry(); + } + + abstract Iterator> entryIterator(); + + private transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + Set> createEntrySet() { + return new Multisets.EntrySet() { + @Override Multiset multiset() { + return DescendingMultiset.this; + } + + @Override public Iterator> iterator() { + return entryIterator(); + } + + @Override public int size() { + return forwardMultiset().entrySet().size(); + } + }; + } + + @Override public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @Override public Object[] toArray() { + return standardToArray(); + } + + @Override public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override public String toString() { + return entrySet().toString(); + } + } +} diff --git a/guava/src/com/google/common/collect/SortedSetMultimap.java b/guava/src/com/google/common/collect/SortedSetMultimap.java new file mode 100644 index 0000000..1b9c641 --- /dev/null +++ b/guava/src/com/google/common/collect/SortedSetMultimap.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A {@code SetMultimap} whose set of values for a given key are kept sorted; + * that is, they comprise a {@link SortedSet}. It cannot hold duplicate + * key-value pairs; adding a key-value pair that's already in the multimap has + * no effect. This interface does not specify the ordering of the multimap's + * keys. See the {@link Multimap} documentation for information common to all + * multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link SortedSet} of values, while {@link Multimap#entries()} + * returns a {@link Set} of map entries. Though the method signature doesn't say + * so explicitly, the map returned by {@link #asMap} has {@code SortedSet} + * values. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface SortedSetMultimap extends SetMultimap { + // Following Javadoc copied from Multimap. + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + @Override + SortedSet get(@Nullable K key); + + /** + * Removes all values associated with a given key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + @Override + SortedSet removeAll(@Nullable Object key); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override + SortedSet replaceValues(K key, Iterable values); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, will + * update the underlying multimap. The map does not support {@code setValue()} + * on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code + * asMap().get(Object)} has the same behavior as {@link #get}, returning a + * live collection. When passed a key that is not present, however, {@code + * asMap().get(Object)} returns {@code null} instead of an empty collection. + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link SortedSet} values. + */ + @Override + Map> asMap(); + + /** + * Returns the comparator that orders the multimap values, with {@code null} + * indicating that natural ordering is used. + */ + Comparator valueComparator(); +} diff --git a/guava/src/com/google/common/collect/StandardRowSortedTable.java b/guava/src/com/google/common/collect/StandardRowSortedTable.java new file mode 100644 index 0000000..fbc7518 --- /dev/null +++ b/guava/src/com/google/common/collect/StandardRowSortedTable.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; + +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Implementation of {@code Table} whose iteration ordering across row keys is + * sorted by their natural ordering or by a supplied comparator. Note that + * iterations across the columns keys for a single row key may or may not be + * ordered, depending on the implementation. When rows and columns are both + * sorted, it's easier to use the {@link TreeBasedTable} subclass. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link + * #rowMap} method returns a {@link SortedMap}, instead of the {@link Set} and + * {@link Map} specified by the {@link Table} interface. + * + *

Null keys and values are not supported. + * + *

See the {@link StandardTable} superclass for more information about the + * behavior of this class. + * + * @author Jared Levy + */ +@GwtCompatible +class StandardRowSortedTable extends StandardTable + implements RowSortedTable { + /* + * TODO(jlevy): Consider adding headTable, tailTable, and subTable methods, + * which return a Table view with rows keys in a given range. Create a + * RowSortedTable subinterface with the revised methods? + */ + + StandardRowSortedTable(SortedMap> backingMap, + Supplier> factory) { + super(backingMap, factory); + } + + private SortedMap> sortedBackingMap() { + return (SortedMap>) backingMap; + } + + private transient SortedSet rowKeySet; + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedSet}, instead of the {@code Set} + * specified in the {@link Table} interface. + */ + @Override public SortedSet rowKeySet() { + SortedSet result = rowKeySet; + return (result == null) ? rowKeySet = new RowKeySortedSet() : result; + } + + private class RowKeySortedSet extends RowKeySet implements SortedSet { + @Override + public Comparator comparator() { + return sortedBackingMap().comparator(); + } + + @Override + public R first() { + return sortedBackingMap().firstKey(); + } + + @Override + public R last() { + return sortedBackingMap().lastKey(); + } + + @Override + public SortedSet headSet(R toElement) { + checkNotNull(toElement); + return new StandardRowSortedTable( + sortedBackingMap().headMap(toElement), factory).rowKeySet(); + } + + @Override + public SortedSet subSet(R fromElement, R toElement) { + checkNotNull(fromElement); + checkNotNull(toElement); + return new StandardRowSortedTable( + sortedBackingMap().subMap(fromElement, toElement), factory) + .rowKeySet(); + } + + @Override + public SortedSet tailSet(R fromElement) { + checkNotNull(fromElement); + return new StandardRowSortedTable( + sortedBackingMap().tailMap(fromElement), factory).rowKeySet(); + } + } + + private transient RowSortedMap rowMap; + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedMap}, instead of the {@code Map} + * specified in the {@link Table} interface. + */ + @Override public SortedMap> rowMap() { + RowSortedMap result = rowMap; + return (result == null) ? rowMap = new RowSortedMap() : result; + } + + private class RowSortedMap extends RowMap implements SortedMap> { + @Override + public Comparator comparator() { + return sortedBackingMap().comparator(); + } + + @Override + public R firstKey() { + return sortedBackingMap().firstKey(); + } + + @Override + public R lastKey() { + return sortedBackingMap().lastKey(); + } + + @Override + public SortedMap> headMap(R toKey) { + checkNotNull(toKey); + return new StandardRowSortedTable( + sortedBackingMap().headMap(toKey), factory).rowMap(); + } + + @Override + public SortedMap> subMap(R fromKey, R toKey) { + checkNotNull(fromKey); + checkNotNull(toKey); + return new StandardRowSortedTable( + sortedBackingMap().subMap(fromKey, toKey), factory).rowMap(); + } + + @Override + public SortedMap> tailMap(R fromKey) { + checkNotNull(fromKey); + return new StandardRowSortedTable( + sortedBackingMap().tailMap(fromKey), factory).rowMap(); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/StandardTable.java b/guava/src/com/google/common/collect/StandardTable.java new file mode 100644 index 0000000..c7329c1 --- /dev/null +++ b/guava/src/com/google/common/collect/StandardTable.java @@ -0,0 +1,1122 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.safeContainsKey; +import static com.google.common.collect.Maps.safeGet; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * {@link Table} implementation backed by a map that associates row keys with + * column key / value secondary maps. This class provides rapid access to + * records by the row key alone or by both keys, but not by just the column key. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link + * #columnMap()} have iterators that don't support {@code remove()}. Otherwise, + * all optional operations are supported. Null row keys, columns keys, and + * values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because + * the data is stored in a {@code Map>}. A method call like {@code + * column(columnKey).get(rowKey)} still runs quickly, since the row key is + * provided. However, {@code column(columnKey).size()} takes longer, since an + * iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads + * access this table concurrently and one of the threads modifies the table, it + * must be synchronized externally. + * + * @author Jared Levy + */ +@GwtCompatible +class StandardTable implements Table, Serializable { + @GwtTransient final Map> backingMap; + @GwtTransient final Supplier> factory; + + StandardTable(Map> backingMap, + Supplier> factory) { + this.backingMap = backingMap; + this.factory = factory; + } + + // Accessors + + @Override public boolean contains( + @Nullable Object rowKey, @Nullable Object columnKey) { + if ((rowKey == null) || (columnKey == null)) { + return false; + } + Map map = safeGet(backingMap, rowKey); + return map != null && safeContainsKey(map, columnKey); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + if (columnKey == null) { + return false; + } + for (Map map : backingMap.values()) { + if (safeContainsKey(map, columnKey)) { + return true; + } + } + return false; + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return rowKey != null && safeContainsKey(backingMap, rowKey); + } + + @Override public boolean containsValue(@Nullable Object value) { + if (value == null) { + return false; + } + for (Map map : backingMap.values()) { + if (map.containsValue(value)) { + return true; + } + } + return false; + } + + @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + if ((rowKey == null) || (columnKey == null)) { + return null; + } + Map map = safeGet(backingMap, rowKey); + return map == null ? null : safeGet(map, columnKey); + } + + @Override public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override public int size() { + int size = 0; + for (Map map : backingMap.values()) { + size += map.size(); + } + return size; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Table) { + Table other = (Table) obj; + return cellSet().equals(other.cellSet()); + } + return false; + } + + @Override public int hashCode() { + return cellSet().hashCode(); + } + + /** + * Returns the string representation {@code rowMap().toString()}. + */ + @Override public String toString() { + return rowMap().toString(); + } + + // Mutators + + @Override public void clear() { + backingMap.clear(); + } + + private Map getOrCreate(R rowKey) { + Map map = backingMap.get(rowKey); + if (map == null) { + map = factory.get(); + backingMap.put(rowKey, map); + } + return map; + } + + @Override public V put(R rowKey, C columnKey, V value) { + checkNotNull(rowKey); + checkNotNull(columnKey); + checkNotNull(value); + return getOrCreate(rowKey).put(columnKey, value); + } + + @Override public void putAll( + Table table) { + for (Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + @Override public V remove( + @Nullable Object rowKey, @Nullable Object columnKey) { + if ((rowKey == null) || (columnKey == null)) { + return null; + } + Map map = safeGet(backingMap, rowKey); + if (map == null) { + return null; + } + V value = map.remove(columnKey); + if (map.isEmpty()) { + backingMap.remove(rowKey); + } + return value; + } + + private Map removeColumn(Object column) { + Map output = new LinkedHashMap(); + Iterator>> iterator + = backingMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + V value = entry.getValue().remove(column); + if (value != null) { + output.put(entry.getKey(), value); + if (entry.getValue().isEmpty()) { + iterator.remove(); + } + } + } + return output; + } + + private boolean containsMapping( + Object rowKey, Object columnKey, Object value) { + return value != null && value.equals(get(rowKey, columnKey)); + } + + /** Remove a row key / column key / value mapping, if present. */ + private boolean removeMapping(Object rowKey, Object columnKey, Object value) { + if (containsMapping(rowKey, columnKey, value)) { + remove(rowKey, columnKey); + return true; + } + return false; + } + + // Views + + /** + * Abstract collection whose {@code isEmpty()} returns whether the table is + * empty and whose {@code clear()} clears all table mappings. + */ + private abstract class TableCollection extends AbstractCollection { + @Override public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override public void clear() { + backingMap.clear(); + } + } + + /** + * Abstract set whose {@code isEmpty()} returns whether the table is empty and + * whose {@code clear()} clears all table mappings. + */ + private abstract class TableSet extends AbstractSet { + @Override public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override public void clear() { + backingMap.clear(); + } + } + + private transient CellSet cellSet; + + /** + * {@inheritDoc} + * + *

The set's iterator traverses the mappings for the first row, the + * mappings for the second row, and so on. + * + *

Each cell is an immutable snapshot of a row key / column key / value + * mapping, taken at the time the cell is returned by a method call to the + * set or its iterator. + */ + @Override public Set> cellSet() { + CellSet result = cellSet; + return (result == null) ? cellSet = new CellSet() : result; + } + + private class CellSet extends TableSet> { + @Override public Iterator> iterator() { + return new CellIterator(); + } + + @Override public int size() { + return StandardTable.this.size(); + } + + @Override public boolean contains(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + return containsMapping( + cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + return false; + } + + @Override public boolean remove(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + return removeMapping( + cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + return false; + } + } + + private class CellIterator implements Iterator> { + final Iterator>> rowIterator + = backingMap.entrySet().iterator(); + Entry> rowEntry; + Iterator> columnIterator + = Iterators.emptyModifiableIterator(); + + @Override public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override public Cell next() { + if (!columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + Entry columnEntry = columnIterator.next(); + return Tables.immutableCell( + rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + + @Override public Map row(R rowKey) { + return new Row(rowKey); + } + + class Row extends AbstractMap { + final R rowKey; + + Row(R rowKey) { + this.rowKey = checkNotNull(rowKey); + } + + Map backingRowMap; + + Map backingRowMap() { + return (backingRowMap == null + || (backingRowMap.isEmpty() && backingMap.containsKey(rowKey))) + ? backingRowMap = computeBackingRowMap() + : backingRowMap; + } + + Map computeBackingRowMap() { + return backingMap.get(rowKey); + } + + // Call this every time we perform a removal. + void maintainEmptyInvariant() { + if (backingRowMap() != null && backingRowMap.isEmpty()) { + backingMap.remove(rowKey); + backingRowMap = null; + } + } + + @Override + public boolean containsKey(Object key) { + Map backingRowMap = backingRowMap(); + return (key != null && backingRowMap != null) + && Maps.safeContainsKey(backingRowMap, key); + } + + @Override + public V get(Object key) { + Map backingRowMap = backingRowMap(); + return (key != null && backingRowMap != null) + ? Maps.safeGet(backingRowMap, key) + : null; + } + + @Override + public V put(C key, V value) { + checkNotNull(key); + checkNotNull(value); + if (backingRowMap != null && !backingRowMap.isEmpty()) { + return backingRowMap.put(key, value); + } + return StandardTable.this.put(rowKey, key, value); + } + + @Override + public V remove(Object key) { + try { + Map backingRowMap = backingRowMap(); + if (backingRowMap == null) { + return null; + } + V result = backingRowMap.remove(key); + maintainEmptyInvariant(); + return result; + } catch (ClassCastException e) { + return null; + } + } + + @Override + public void clear() { + Map backingRowMap = backingRowMap(); + if (backingRowMap != null) { + backingRowMap.clear(); + } + maintainEmptyInvariant(); + } + + Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + if (result == null) { + return keySet = new Maps.KeySet() { + @Override + Map map() { + return Row.this; + } + }; + } + return result; + } + + Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + return entrySet = new RowEntrySet(); + } + return result; + } + + private class RowEntrySet extends Maps.EntrySet { + @Override + Map map() { + return Row.this; + } + + @Override + public int size() { + Map map = backingRowMap(); + return (map == null) ? 0 : map.size(); + } + + @Override + public Iterator> iterator() { + final Map map = backingRowMap(); + if (map == null) { + return Iterators.emptyModifiableIterator(); + } + final Iterator> iterator = map.entrySet().iterator(); + return new Iterator>() { + @Override public boolean hasNext() { + return iterator.hasNext(); + } + @Override public Entry next() { + final Entry entry = iterator.next(); + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return entry; + } + @Override public V setValue(V value) { + return super.setValue(checkNotNull(value)); + } + @Override + public boolean equals(Object object) { + // TODO(user): identify why this affects GWT tests + return standardEquals(object); + } + }; + } + + @Override + public void remove() { + iterator.remove(); + maintainEmptyInvariant(); + } + }; + } + } + } + + /** + * {@inheritDoc} + * + *

The returned map's views have iterators that don't support + * {@code remove()}. + */ + @Override public Map column(C columnKey) { + return new Column(columnKey); + } + + private class Column extends Maps.ImprovedAbstractMap { + final C columnKey; + + Column(C columnKey) { + this.columnKey = checkNotNull(columnKey); + } + + @Override public V put(R key, V value) { + return StandardTable.this.put(key, columnKey, value); + } + + @Override public V get(Object key) { + return StandardTable.this.get(key, columnKey); + } + + @Override public boolean containsKey(Object key) { + return StandardTable.this.contains(key, columnKey); + } + + @Override public V remove(Object key) { + return StandardTable.this.remove(key, columnKey); + } + + @Override public Set> createEntrySet() { + return new EntrySet(); + } + + Values columnValues; + + @Override public Collection values() { + Values result = columnValues; + return (result == null) ? columnValues = new Values() : result; + } + + /** + * Removes all {@code Column} mappings whose row key and value satisfy the + * given predicate. + */ + boolean removePredicate(Predicate> predicate) { + boolean changed = false; + Iterator>> iterator + = backingMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + Map map = entry.getValue(); + V value = map.get(columnKey); + if (value != null + && predicate.apply( + new ImmutableEntry(entry.getKey(), value))) { + map.remove(columnKey); + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + class EntrySet extends Sets.ImprovedAbstractSet> { + @Override public Iterator> iterator() { + return new EntrySetIterator(); + } + + @Override public int size() { + int size = 0; + for (Map map : backingMap.values()) { + if (map.containsKey(columnKey)) { + size++; + } + } + return size; + } + + @Override public boolean isEmpty() { + return !containsColumn(columnKey); + } + + @Override public void clear() { + Predicate> predicate = Predicates.alwaysTrue(); + removePredicate(predicate); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + return containsMapping(entry.getKey(), columnKey, entry.getValue()); + } + return false; + } + + @Override public boolean remove(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return removeMapping(entry.getKey(), columnKey, entry.getValue()); + } + return false; + } + + @Override public boolean retainAll(Collection c) { + return removePredicate(Predicates.not(Predicates.in(c))); + } + } + + class EntrySetIterator extends AbstractIterator> { + final Iterator>> iterator + = backingMap.entrySet().iterator(); + @Override protected Entry computeNext() { + while (iterator.hasNext()) { + final Entry> entry = iterator.next(); + if (entry.getValue().containsKey(columnKey)) { + return new AbstractMapEntry() { + @Override public R getKey() { + return entry.getKey(); + } + @Override public V getValue() { + return entry.getValue().get(columnKey); + } + @Override public V setValue(V value) { + return entry.getValue().put(columnKey, checkNotNull(value)); + } + }; + } + } + return endOfData(); + } + } + + KeySet keySet; + + @Override public Set keySet() { + KeySet result = keySet; + return result == null ? keySet = new KeySet() : result; + } + + class KeySet extends Sets.ImprovedAbstractSet { + @Override public Iterator iterator() { + return Maps.keyIterator(Column.this.entrySet().iterator()); + } + + @Override public int size() { + return entrySet().size(); + } + + @Override public boolean isEmpty() { + return !containsColumn(columnKey); + } + + @Override public boolean contains(Object obj) { + return StandardTable.this.contains(obj, columnKey); + } + + @Override public boolean remove(Object obj) { + return StandardTable.this.remove(obj, columnKey) != null; + } + + @Override public void clear() { + entrySet().clear(); + } + + @Override public boolean retainAll(final Collection c) { + checkNotNull(c); + Predicate> predicate = new Predicate>() { + @Override + public boolean apply(Entry entry) { + return !c.contains(entry.getKey()); + } + }; + return removePredicate(predicate); + } + } + + class Values extends AbstractCollection { + @Override public Iterator iterator() { + return Maps.valueIterator(Column.this.entrySet().iterator()); + } + + @Override public int size() { + return entrySet().size(); + } + + @Override public boolean isEmpty() { + return !containsColumn(columnKey); + } + + @Override public void clear() { + entrySet().clear(); + } + + @Override public boolean remove(Object obj) { + if (obj == null) { + return false; + } + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + if (map.entrySet().remove( + new ImmutableEntry(columnKey, obj))) { + if (map.isEmpty()) { + iterator.remove(); + } + return true; + } + } + return false; + } + + @Override public boolean removeAll(final Collection c) { + checkNotNull(c); + Predicate> predicate = new Predicate>() { + @Override + public boolean apply(Entry entry) { + return c.contains(entry.getValue()); + } + }; + return removePredicate(predicate); + } + + @Override public boolean retainAll(final Collection c) { + checkNotNull(c); + Predicate> predicate = new Predicate>() { + @Override + public boolean apply(Entry entry) { + return !c.contains(entry.getValue()); + } + }; + return removePredicate(predicate); + } + } + } + + private transient RowKeySet rowKeySet; + + @Override public Set rowKeySet() { + Set result = rowKeySet; + return (result == null) ? rowKeySet = new RowKeySet() : result; + } + + class RowKeySet extends TableSet { + @Override public Iterator iterator() { + return Maps.keyIterator(rowMap().entrySet().iterator()); + } + + @Override public int size() { + return backingMap.size(); + } + + @Override public boolean contains(Object obj) { + return containsRow(obj); + } + + @Override public boolean remove(Object obj) { + return (obj != null) && backingMap.remove(obj) != null; + } + } + + private transient Set columnKeySet; + + /** + * {@inheritDoc} + * + *

The returned set has an iterator that does not support {@code remove()}. + * + *

The set's iterator traverses the columns of the first row, the + * columns of the second row, etc., skipping any columns that have + * appeared previously. + */ + @Override + public Set columnKeySet() { + Set result = columnKeySet; + return (result == null) ? columnKeySet = new ColumnKeySet() : result; + } + + private class ColumnKeySet extends TableSet { + @Override public Iterator iterator() { + return createColumnKeyIterator(); + } + + @Override public int size() { + return Iterators.size(iterator()); + } + + @Override public boolean remove(Object obj) { + if (obj == null) { + return false; + } + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + if (map.keySet().remove(obj)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override public boolean removeAll(Collection c) { + checkNotNull(c); + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + // map.keySet().removeAll(c) can throw a NPE when map is a TreeMap with + // natural ordering and c contains a null. + if (Iterators.removeAll(map.keySet().iterator(), c)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + checkNotNull(c); + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + if (map.keySet().retainAll(c)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override public boolean contains(Object obj) { + if (obj == null) { + return false; + } + for (Map map : backingMap.values()) { + if (map.containsKey(obj)) { + return true; + } + } + return false; + } + } + + /** + * Creates an iterator that returns each column value with duplicates + * omitted. + */ + Iterator createColumnKeyIterator() { + return new ColumnKeyIterator(); + } + + private class ColumnKeyIterator extends AbstractIterator { + // Use the same map type to support TreeMaps with comparators that aren't + // consistent with equals(). + final Map seen = factory.get(); + final Iterator> mapIterator = backingMap.values().iterator(); + Iterator> entryIterator = Iterators.emptyIterator(); + + @Override protected C computeNext() { + while (true) { + if (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + if (!seen.containsKey(entry.getKey())) { + seen.put(entry.getKey(), entry.getValue()); + return entry.getKey(); + } + } else if (mapIterator.hasNext()) { + entryIterator = mapIterator.next().entrySet().iterator(); + } else { + return endOfData(); + } + } + } + } + + private transient Values values; + + /** + * {@inheritDoc} + * + *

The collection's iterator traverses the values for the first row, + * the values for the second row, and so on. + */ + @Override public Collection values() { + Values result = values; + return (result == null) ? values = new Values() : result; + } + + private class Values extends TableCollection { + @Override public Iterator iterator() { + return new TransformedIterator, V>(cellSet().iterator()) { + @Override + V transform(Cell cell) { + return cell.getValue(); + } + }; + } + + @Override public int size() { + return StandardTable.this.size(); + } + } + + private transient RowMap rowMap; + + @Override public Map> rowMap() { + RowMap result = rowMap; + return (result == null) ? rowMap = new RowMap() : result; + } + + class RowMap extends Maps.ImprovedAbstractMap> { + @Override public boolean containsKey(Object key) { + return containsRow(key); + } + + // performing cast only when key is in backing map and has the correct type + @SuppressWarnings("unchecked") + @Override public Map get(Object key) { + return containsRow(key) ? row((R) key) : null; + } + + @Override public Set keySet() { + return rowKeySet(); + } + + @Override public Map remove(Object key) { + return (key == null) ? null : backingMap.remove(key); + } + + @Override protected Set>> createEntrySet() { + return new EntrySet(); + } + + class EntrySet extends TableSet>> { + @Override public Iterator>> iterator() { + return new TransformedIterator>>( + backingMap.keySet().iterator()) { + @Override + Entry> transform(R rowKey) { + return new ImmutableEntry>(rowKey, row(rowKey)); + } + }; + } + + @Override public int size() { + return backingMap.size(); + } + + @Override public boolean contains(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return entry.getKey() != null + && entry.getValue() instanceof Map + && Collections2.safeContains(backingMap.entrySet(), entry); + } + return false; + } + + @Override public boolean remove(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return entry.getKey() != null + && entry.getValue() instanceof Map + && backingMap.entrySet().remove(entry); + } + return false; + } + } + } + + private transient ColumnMap columnMap; + + @Override public Map> columnMap() { + ColumnMap result = columnMap; + return (result == null) ? columnMap = new ColumnMap() : result; + } + + private class ColumnMap extends Maps.ImprovedAbstractMap> { + // The cast to C occurs only when the key is in the map, implying that it + // has the correct type. + @SuppressWarnings("unchecked") + @Override public Map get(Object key) { + return containsColumn(key) ? column((C) key) : null; + } + + @Override public boolean containsKey(Object key) { + return containsColumn(key); + } + + @Override public Map remove(Object key) { + return containsColumn(key) ? removeColumn(key) : null; + } + + @Override public Set>> createEntrySet() { + return new ColumnMapEntrySet(); + } + + @Override public Set keySet() { + return columnKeySet(); + } + + ColumnMapValues columnMapValues; + + @Override public Collection> values() { + ColumnMapValues result = columnMapValues; + return + (result == null) ? columnMapValues = new ColumnMapValues() : result; + } + + class ColumnMapEntrySet extends TableSet>> { + @Override public Iterator>> iterator() { + return new TransformedIterator>>( + columnKeySet().iterator()) { + @Override + Entry> transform(C columnKey) { + return new ImmutableEntry>( + columnKey, column(columnKey)); + } + }; + } + + @Override public int size() { + return columnKeySet().size(); + } + + @Override public boolean contains(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + if (containsColumn(entry.getKey())) { + // The cast to C occurs only when the key is in the map, implying + // that it has the correct type. + @SuppressWarnings("unchecked") + C columnKey = (C) entry.getKey(); + return get(columnKey).equals(entry.getValue()); + } + } + return false; + } + + @Override public boolean remove(Object obj) { + if (contains(obj)) { + Entry entry = (Entry) obj; + removeColumn(entry.getKey()); + return true; + } + return false; + } + + @Override public boolean removeAll(Collection c) { + boolean changed = false; + for (Object obj : c) { + changed |= remove(obj); + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (!c.contains(new ImmutableEntry>( + columnKey, column(columnKey)))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + } + + private class ColumnMapValues extends TableCollection> { + @Override public Iterator> iterator() { + return Maps.valueIterator(ColumnMap.this.entrySet().iterator()); + } + + @Override public boolean remove(Object obj) { + for (Entry> entry : ColumnMap.this.entrySet()) { + if (entry.getValue().equals(obj)) { + removeColumn(entry.getKey()); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection c) { + checkNotNull(c); + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (c.contains(column(columnKey))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + checkNotNull(c); + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (!c.contains(column(columnKey))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + + @Override public int size() { + return columnKeySet().size(); + } + } + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/Synchronized.java b/guava/src/com/google/common/collect/Synchronized.java new file mode 100644 index 0000000..4262b27 --- /dev/null +++ b/guava/src/com/google/common/collect/Synchronized.java @@ -0,0 +1,1565 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Synchronized collection views. The returned synchronized collection views are + * serializable if the backing collection and the mutex are serializable. + * + *

If {@code null} is passed as the {@code mutex} parameter to any of this + * class's top-level methods or inner class constructors, the created object + * uses itself as the synchronization mutex. + * + *

This class should be used by other collection classes only. + * + * @author Mike Bostock + * @author Jared Levy + */ +@GwtCompatible(emulated = true) +final class Synchronized { + private Synchronized() {} + + static class SynchronizedObject implements Serializable { + final Object delegate; + final Object mutex; + + SynchronizedObject(Object delegate, @Nullable Object mutex) { + this.delegate = checkNotNull(delegate); + this.mutex = (mutex == null) ? this : mutex; + } + + Object delegate() { + return delegate; + } + + // No equals and hashCode; see ForwardingObject for details. + + @Override public String toString() { + synchronized (mutex) { + return delegate.toString(); + } + } + + // Serialization invokes writeObject only when it's private. + // The SynchronizedObject subclasses don't need a writeObject method since + // they don't contain any non-transient member variables, while the + // following writeObject() handles the SynchronizedObject members. + + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + synchronized (mutex) { + stream.defaultWriteObject(); + } + } + + @GwtIncompatible("not needed in emulated source") + private static final long serialVersionUID = 0; + } + + private static Collection collection( + Collection collection, @Nullable Object mutex) { + return new SynchronizedCollection(collection, mutex); + } + + @VisibleForTesting static class SynchronizedCollection + extends SynchronizedObject implements Collection { + private SynchronizedCollection( + Collection delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override Collection delegate() { + return (Collection) super.delegate(); + } + + @Override + public boolean add(E e) { + synchronized (mutex) { + return delegate().add(e); + } + } + + @Override + public boolean addAll(Collection c) { + synchronized (mutex) { + return delegate().addAll(c); + } + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public boolean contains(Object o) { + synchronized (mutex) { + return delegate().contains(o); + } + } + + @Override + public boolean containsAll(Collection c) { + synchronized (mutex) { + return delegate().containsAll(c); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public Iterator iterator() { + return delegate().iterator(); // manually synchronized + } + + @Override + public boolean remove(Object o) { + synchronized (mutex) { + return delegate().remove(o); + } + } + + @Override + public boolean removeAll(Collection c) { + synchronized (mutex) { + return delegate().removeAll(c); + } + } + + @Override + public boolean retainAll(Collection c) { + synchronized (mutex) { + return delegate().retainAll(c); + } + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public Object[] toArray() { + synchronized (mutex) { + return delegate().toArray(); + } + } + + @Override + public T[] toArray(T[] a) { + synchronized (mutex) { + return delegate().toArray(a); + } + } + + private static final long serialVersionUID = 0; + } + + @VisibleForTesting static Set set(Set set, @Nullable Object mutex) { + return new SynchronizedSet(set, mutex); + } + + static class SynchronizedSet + extends SynchronizedCollection implements Set { + + SynchronizedSet(Set delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override Set delegate() { + return (Set) super.delegate(); + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + private static SortedSet sortedSet( + SortedSet set, @Nullable Object mutex) { + return new SynchronizedSortedSet(set, mutex); + } + + static class SynchronizedSortedSet extends SynchronizedSet + implements SortedSet { + SynchronizedSortedSet(SortedSet delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override SortedSet delegate() { + return (SortedSet) super.delegate(); + } + + @Override + public Comparator comparator() { + synchronized (mutex) { + return delegate().comparator(); + } + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + synchronized (mutex) { + return sortedSet(delegate().subSet(fromElement, toElement), mutex); + } + } + + @Override + public SortedSet headSet(E toElement) { + synchronized (mutex) { + return sortedSet(delegate().headSet(toElement), mutex); + } + } + + @Override + public SortedSet tailSet(E fromElement) { + synchronized (mutex) { + return sortedSet(delegate().tailSet(fromElement), mutex); + } + } + + @Override + public E first() { + synchronized (mutex) { + return delegate().first(); + } + } + + @Override + public E last() { + synchronized (mutex) { + return delegate().last(); + } + } + + private static final long serialVersionUID = 0; + } + + private static List list(List list, @Nullable Object mutex) { + return (list instanceof RandomAccess) + ? new SynchronizedRandomAccessList(list, mutex) + : new SynchronizedList(list, mutex); + } + + private static class SynchronizedList extends SynchronizedCollection + implements List { + SynchronizedList(List delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override List delegate() { + return (List) super.delegate(); + } + + @Override + public void add(int index, E element) { + synchronized (mutex) { + delegate().add(index, element); + } + } + + @Override + public boolean addAll(int index, Collection c) { + synchronized (mutex) { + return delegate().addAll(index, c); + } + } + + @Override + public E get(int index) { + synchronized (mutex) { + return delegate().get(index); + } + } + + @Override + public int indexOf(Object o) { + synchronized (mutex) { + return delegate().indexOf(o); + } + } + + @Override + public int lastIndexOf(Object o) { + synchronized (mutex) { + return delegate().lastIndexOf(o); + } + } + + @Override + public ListIterator listIterator() { + return delegate().listIterator(); // manually synchronized + } + + @Override + public ListIterator listIterator(int index) { + return delegate().listIterator(index); // manually synchronized + } + + @Override + public E remove(int index) { + synchronized (mutex) { + return delegate().remove(index); + } + } + + @Override + public E set(int index, E element) { + synchronized (mutex) { + return delegate().set(index, element); + } + } + + @Override + public List subList(int fromIndex, int toIndex) { + synchronized (mutex) { + return list(delegate().subList(fromIndex, toIndex), mutex); + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedRandomAccessList + extends SynchronizedList implements RandomAccess { + SynchronizedRandomAccessList(List list, @Nullable Object mutex) { + super(list, mutex); + } + private static final long serialVersionUID = 0; + } + + static Multiset multiset( + Multiset multiset, @Nullable Object mutex) { + if (multiset instanceof SynchronizedMultiset || + multiset instanceof ImmutableMultiset) { + return multiset; + } + return new SynchronizedMultiset(multiset, mutex); + } + + private static class SynchronizedMultiset extends SynchronizedCollection + implements Multiset { + transient Set elementSet; + transient Set> entrySet; + + SynchronizedMultiset(Multiset delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override Multiset delegate() { + return (Multiset) super.delegate(); + } + + @Override + public int count(Object o) { + synchronized (mutex) { + return delegate().count(o); + } + } + + @Override + public int add(E e, int n) { + synchronized (mutex) { + return delegate().add(e, n); + } + } + + @Override + public int remove(Object o, int n) { + synchronized (mutex) { + return delegate().remove(o, n); + } + } + + @Override + public int setCount(E element, int count) { + synchronized (mutex) { + return delegate().setCount(element, count); + } + } + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + synchronized (mutex) { + return delegate().setCount(element, oldCount, newCount); + } + } + + @Override + public Set elementSet() { + synchronized (mutex) { + if (elementSet == null) { + elementSet = typePreservingSet(delegate().elementSet(), mutex); + } + return elementSet; + } + } + + @Override + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = typePreservingSet(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static Multimap multimap( + Multimap multimap, @Nullable Object mutex) { + if (multimap instanceof SynchronizedMultimap || + multimap instanceof ImmutableMultimap) { + return multimap; + } + return new SynchronizedMultimap(multimap, mutex); + } + + private static class SynchronizedMultimap extends SynchronizedObject + implements Multimap { + transient Set keySet; + transient Collection valuesCollection; + transient Collection> entries; + transient Map> asMap; + transient Multiset keys; + + @SuppressWarnings("unchecked") + @Override Multimap delegate() { + return (Multimap) super.delegate(); + } + + SynchronizedMultimap(Multimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + @Override + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + @Override + public boolean containsEntry(Object key, Object value) { + synchronized (mutex) { + return delegate().containsEntry(key, value); + } + } + + @Override + public Collection get(K key) { + synchronized (mutex) { + return typePreservingCollection(delegate().get(key), mutex); + } + } + + @Override + public boolean put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + @Override + public boolean putAll(K key, Iterable values) { + synchronized (mutex) { + return delegate().putAll(key, values); + } + } + + @Override + public boolean putAll(Multimap multimap) { + synchronized (mutex) { + return delegate().putAll(multimap); + } + } + + @Override + public Collection replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + @Override + public boolean remove(Object key, Object value) { + synchronized (mutex) { + return delegate().remove(key, value); + } + } + + @Override + public Collection removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = typePreservingSet(delegate().keySet(), mutex); + } + return keySet; + } + } + + @Override + public Collection values() { + synchronized (mutex) { + if (valuesCollection == null) { + valuesCollection = collection(delegate().values(), mutex); + } + return valuesCollection; + } + } + + @Override + public Collection> entries() { + synchronized (mutex) { + if (entries == null) { + entries = typePreservingCollection(delegate().entries(), mutex); + } + return entries; + } + } + + @Override + public Map> asMap() { + synchronized (mutex) { + if (asMap == null) { + asMap = new SynchronizedAsMap(delegate().asMap(), mutex); + } + return asMap; + } + } + + @Override + public Multiset keys() { + synchronized (mutex) { + if (keys == null) { + keys = multiset(delegate().keys(), mutex); + } + return keys; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static ListMultimap listMultimap( + ListMultimap multimap, @Nullable Object mutex) { + if (multimap instanceof SynchronizedListMultimap || + multimap instanceof ImmutableListMultimap) { + return multimap; + } + return new SynchronizedListMultimap(multimap, mutex); + } + + private static class SynchronizedListMultimap + extends SynchronizedMultimap implements ListMultimap { + SynchronizedListMultimap( + ListMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + @Override public List get(K key) { + synchronized (mutex) { + return list(delegate().get(key), mutex); + } + } + @Override public List removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public List replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + private static final long serialVersionUID = 0; + } + + static SetMultimap setMultimap( + SetMultimap multimap, @Nullable Object mutex) { + if (multimap instanceof SynchronizedSetMultimap || + multimap instanceof ImmutableSetMultimap) { + return multimap; + } + return new SynchronizedSetMultimap(multimap, mutex); + } + + private static class SynchronizedSetMultimap + extends SynchronizedMultimap implements SetMultimap { + transient Set> entrySet; + + SynchronizedSetMultimap( + SetMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + @Override public Set get(K key) { + synchronized (mutex) { + return set(delegate().get(key), mutex); + } + } + @Override public Set removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public Set replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + @Override public Set> entries() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entries(), mutex); + } + return entrySet; + } + } + private static final long serialVersionUID = 0; + } + + static SortedSetMultimap sortedSetMultimap( + SortedSetMultimap multimap, @Nullable Object mutex) { + if (multimap instanceof SynchronizedSortedSetMultimap) { + return multimap; + } + return new SynchronizedSortedSetMultimap(multimap, mutex); + } + + private static class SynchronizedSortedSetMultimap + extends SynchronizedSetMultimap implements SortedSetMultimap { + SynchronizedSortedSetMultimap( + SortedSetMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + @Override public SortedSet get(K key) { + synchronized (mutex) { + return sortedSet(delegate().get(key), mutex); + } + } + @Override public SortedSet removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public SortedSet replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + @Override + public Comparator valueComparator() { + synchronized (mutex) { + return delegate().valueComparator(); + } + } + private static final long serialVersionUID = 0; + } + + private static Collection typePreservingCollection( + Collection collection, @Nullable Object mutex) { + if (collection instanceof SortedSet) { + return sortedSet((SortedSet) collection, mutex); + } + if (collection instanceof Set) { + return set((Set) collection, mutex); + } + if (collection instanceof List) { + return list((List) collection, mutex); + } + return collection(collection, mutex); + } + + private static Set typePreservingSet( + Set set, @Nullable Object mutex) { + if (set instanceof SortedSet) { + return sortedSet((SortedSet) set, mutex); + } else { + return set(set, mutex); + } + } + + private static class SynchronizedAsMapEntries + extends SynchronizedSet>> { + SynchronizedAsMapEntries( + Set>> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Iterator>> iterator() { + // Must be manually synchronized. + final Iterator>> iterator = super.iterator(); + return new ForwardingIterator>>() { + @Override protected Iterator>> delegate() { + return iterator; + } + + @Override public Map.Entry> next() { + final Map.Entry> entry = super.next(); + return new ForwardingMapEntry>() { + @Override protected Map.Entry> delegate() { + return entry; + } + @Override public Collection getValue() { + return typePreservingCollection(entry.getValue(), mutex); + } + }; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate()); + } + } + @Override public T[] toArray(T[] array) { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate(), array); + } + } + @Override public boolean contains(Object o) { + synchronized (mutex) { + return Maps.containsEntryImpl(delegate(), o); + } + } + @Override public boolean containsAll(Collection c) { + synchronized (mutex) { + return Collections2.containsAllImpl(delegate(), c); + } + } + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return Sets.equalsImpl(delegate(), o); + } + } + @Override public boolean remove(Object o) { + synchronized (mutex) { + return Maps.removeEntryImpl(delegate(), o); + } + } + @Override public boolean removeAll(Collection c) { + synchronized (mutex) { + return Iterators.removeAll(delegate().iterator(), c); + } + } + @Override public boolean retainAll(Collection c) { + synchronized (mutex) { + return Iterators.retainAll(delegate().iterator(), c); + } + } + + private static final long serialVersionUID = 0; + } + + @VisibleForTesting + static Map map(Map map, @Nullable Object mutex) { + return new SynchronizedMap(map, mutex); + } + + private static class SynchronizedMap extends SynchronizedObject + implements Map { + transient Set keySet; + transient Collection values; + transient Set> entrySet; + + SynchronizedMap(Map delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override Map delegate() { + return (Map) super.delegate(); + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + @Override + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + @Override + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + @Override + public V get(Object key) { + synchronized (mutex) { + return delegate().get(key); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = set(delegate().keySet(), mutex); + } + return keySet; + } + } + + @Override + public V put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + @Override + public void putAll(Map map) { + synchronized (mutex) { + delegate().putAll(map); + } + } + + @Override + public V remove(Object key) { + synchronized (mutex) { + return delegate().remove(key); + } + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public Collection values() { + synchronized (mutex) { + if (values == null) { + values = collection(delegate().values(), mutex); + } + return values; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static SortedMap sortedMap( + SortedMap sortedMap, @Nullable Object mutex) { + return new SynchronizedSortedMap(sortedMap, mutex); + } + + static class SynchronizedSortedMap extends SynchronizedMap + implements SortedMap { + + SynchronizedSortedMap(SortedMap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override SortedMap delegate() { + return (SortedMap) super.delegate(); + } + + @Override public Comparator comparator() { + synchronized (mutex) { + return delegate().comparator(); + } + } + + @Override public K firstKey() { + synchronized (mutex) { + return delegate().firstKey(); + } + } + + @Override public SortedMap headMap(K toKey) { + synchronized (mutex) { + return sortedMap(delegate().headMap(toKey), mutex); + } + } + + @Override public K lastKey() { + synchronized (mutex) { + return delegate().lastKey(); + } + } + + @Override public SortedMap subMap(K fromKey, K toKey) { + synchronized (mutex) { + return sortedMap(delegate().subMap(fromKey, toKey), mutex); + } + } + + @Override public SortedMap tailMap(K fromKey) { + synchronized (mutex) { + return sortedMap(delegate().tailMap(fromKey), mutex); + } + } + + private static final long serialVersionUID = 0; + } + + static BiMap biMap(BiMap bimap, @Nullable Object mutex) { + if (bimap instanceof SynchronizedBiMap || + bimap instanceof ImmutableBiMap) { + return bimap; + } + return new SynchronizedBiMap(bimap, mutex, null); + } + + @VisibleForTesting static class SynchronizedBiMap + extends SynchronizedMap implements BiMap, Serializable { + private transient Set valueSet; + private transient BiMap inverse; + + private SynchronizedBiMap(BiMap delegate, @Nullable Object mutex, + @Nullable BiMap inverse) { + super(delegate, mutex); + this.inverse = inverse; + } + + @Override BiMap delegate() { + return (BiMap) super.delegate(); + } + + @Override public Set values() { + synchronized (mutex) { + if (valueSet == null) { + valueSet = set(delegate().values(), mutex); + } + return valueSet; + } + } + + @Override + public V forcePut(K key, V value) { + synchronized (mutex) { + return delegate().forcePut(key, value); + } + } + + @Override + public BiMap inverse() { + synchronized (mutex) { + if (inverse == null) { + inverse + = new SynchronizedBiMap(delegate().inverse(), mutex, this); + } + return inverse; + } + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedAsMap + extends SynchronizedMap> { + transient Set>> asMapEntrySet; + transient Collection> asMapValues; + + SynchronizedAsMap(Map> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Collection get(Object key) { + synchronized (mutex) { + Collection collection = super.get(key); + return (collection == null) ? null + : typePreservingCollection(collection, mutex); + } + } + + @Override public Set>> entrySet() { + synchronized (mutex) { + if (asMapEntrySet == null) { + asMapEntrySet = new SynchronizedAsMapEntries( + delegate().entrySet(), mutex); + } + return asMapEntrySet; + } + } + + @Override public Collection> values() { + synchronized (mutex) { + if (asMapValues == null) { + asMapValues + = new SynchronizedAsMapValues(delegate().values(), mutex); + } + return asMapValues; + } + } + + @Override public boolean containsValue(Object o) { + // values() and its contains() method are both synchronized. + return values().contains(o); + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedAsMapValues + extends SynchronizedCollection> { + SynchronizedAsMapValues( + Collection> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Iterator> iterator() { + // Must be manually synchronized. + final Iterator> iterator = super.iterator(); + return new ForwardingIterator>() { + @Override protected Iterator> delegate() { + return iterator; + } + @Override public Collection next() { + return typePreservingCollection(super.next(), mutex); + } + }; + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("NavigableSet") + @VisibleForTesting + static class SynchronizedNavigableSet extends SynchronizedSortedSet + implements NavigableSet { + SynchronizedNavigableSet(NavigableSet delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override NavigableSet delegate() { + return (NavigableSet) super.delegate(); + } + + @Override public E ceiling(E e) { + synchronized (mutex) { + return delegate().ceiling(e); + } + } + + @Override public Iterator descendingIterator() { + return delegate().descendingIterator(); // manually synchronized + } + + transient NavigableSet descendingSet; + + @Override public NavigableSet descendingSet() { + synchronized (mutex) { + if (descendingSet == null) { + NavigableSet dS = + Synchronized.navigableSet(delegate().descendingSet(), mutex); + descendingSet = dS; + return dS; + } + return descendingSet; + } + } + + @Override public E floor(E e) { + synchronized (mutex) { + return delegate().floor(e); + } + } + + @Override public NavigableSet headSet(E toElement, boolean inclusive) { + synchronized (mutex) { + return Synchronized.navigableSet( + delegate().headSet(toElement, inclusive), mutex); + } + } + + @Override public E higher(E e) { + synchronized (mutex) { + return delegate().higher(e); + } + } + + @Override public E lower(E e) { + synchronized (mutex) { + return delegate().lower(e); + } + } + + @Override public E pollFirst() { + synchronized (mutex) { + return delegate().pollFirst(); + } + } + + @Override public E pollLast() { + synchronized (mutex) { + return delegate().pollLast(); + } + } + + @Override public NavigableSet subSet(E fromElement, + boolean fromInclusive, E toElement, boolean toInclusive) { + synchronized (mutex) { + return Synchronized.navigableSet(delegate().subSet( + fromElement, fromInclusive, toElement, toInclusive), mutex); + } + } + + @Override public NavigableSet tailSet(E fromElement, boolean inclusive) { + synchronized (mutex) { + return Synchronized.navigableSet( + delegate().tailSet(fromElement, inclusive), mutex); + } + } + + @Override public SortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + @Override public SortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override public SortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("NavigableSet") + static NavigableSet navigableSet( + NavigableSet navigableSet, @Nullable Object mutex) { + return new SynchronizedNavigableSet(navigableSet, mutex); + } + + @GwtIncompatible("NavigableSet") + static NavigableSet navigableSet(NavigableSet navigableSet) { + return navigableSet(navigableSet, null); + } + + @GwtIncompatible("NavigableMap") + static NavigableMap navigableMap( + NavigableMap navigableMap) { + return navigableMap(navigableMap, null); + } + + @GwtIncompatible("NavigableMap") + static NavigableMap navigableMap( + NavigableMap navigableMap, @Nullable Object mutex) { + return new SynchronizedNavigableMap(navigableMap, mutex); + } + + @GwtIncompatible("NavigableMap") + @VisibleForTesting static class SynchronizedNavigableMap + extends SynchronizedSortedMap implements NavigableMap { + + SynchronizedNavigableMap( + NavigableMap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override NavigableMap delegate() { + return (NavigableMap) super.delegate(); + } + + @Override public Entry ceilingEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().ceilingEntry(key), mutex); + } + } + + @Override public K ceilingKey(K key) { + synchronized (mutex) { + return delegate().ceilingKey(key); + } + } + + transient NavigableSet descendingKeySet; + + @Override public NavigableSet descendingKeySet() { + synchronized (mutex) { + if (descendingKeySet == null) { + return descendingKeySet = + Synchronized.navigableSet(delegate().descendingKeySet(), mutex); + } + return descendingKeySet; + } + } + + transient NavigableMap descendingMap; + + @Override public NavigableMap descendingMap() { + synchronized (mutex) { + if (descendingMap == null) { + return descendingMap = + navigableMap(delegate().descendingMap(), mutex); + } + return descendingMap; + } + } + + @Override public Entry firstEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().firstEntry(), mutex); + } + } + + @Override public Entry floorEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().floorEntry(key), mutex); + } + } + + @Override public K floorKey(K key) { + synchronized (mutex) { + return delegate().floorKey(key); + } + } + + @Override public NavigableMap headMap(K toKey, boolean inclusive) { + synchronized (mutex) { + return navigableMap( + delegate().headMap(toKey, inclusive), mutex); + } + } + + @Override public Entry higherEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().higherEntry(key), mutex); + } + } + + @Override public K higherKey(K key) { + synchronized (mutex) { + return delegate().higherKey(key); + } + } + + @Override public Entry lastEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().lastEntry(), mutex); + } + } + + @Override public Entry lowerEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().lowerEntry(key), mutex); + } + } + + @Override public K lowerKey(K key) { + synchronized (mutex) { + return delegate().lowerKey(key); + } + } + + @Override public Set keySet() { + return navigableKeySet(); + } + + transient NavigableSet navigableKeySet; + + @Override public NavigableSet navigableKeySet() { + synchronized (mutex) { + if (navigableKeySet == null) { + return navigableKeySet = + Synchronized.navigableSet(delegate().navigableKeySet(), mutex); + } + return navigableKeySet; + } + } + + @Override public Entry pollFirstEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().pollFirstEntry(), mutex); + } + } + + @Override public Entry pollLastEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().pollLastEntry(), mutex); + } + } + + @Override public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + synchronized (mutex) { + return navigableMap( + delegate().subMap(fromKey, fromInclusive, toKey, toInclusive), + mutex); + } + } + + @Override public NavigableMap tailMap(K fromKey, boolean inclusive) { + synchronized (mutex) { + return navigableMap( + delegate().tailMap(fromKey, inclusive), mutex); + } + } + + @Override public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible("works but is needed only for NavigableMap") + private static Entry nullableSynchronizedEntry( + @Nullable Entry entry, @Nullable Object mutex) { + if (entry == null) { + return null; + } + return new SynchronizedEntry(entry, mutex); + } + + @GwtIncompatible("works but is needed only for NavigableMap") + private static class SynchronizedEntry extends SynchronizedObject + implements Entry { + + SynchronizedEntry(Entry delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") // guaranteed by the constructor + @Override Entry delegate() { + return (Entry) super.delegate(); + } + + @Override public boolean equals(Object obj) { + synchronized (mutex) { + return delegate().equals(obj); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + @Override public K getKey() { + synchronized (mutex) { + return delegate().getKey(); + } + } + + @Override public V getValue() { + synchronized (mutex) { + return delegate().getValue(); + } + } + + @Override public V setValue(V value) { + synchronized (mutex) { + return delegate().setValue(value); + } + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/collect/Table.java b/guava/src/com/google/common/collect/Table.java new file mode 100644 index 0000000..c384bf8 --- /dev/null +++ b/guava/src/com/google/common/collect/Table.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A collection that associates an ordered pair of keys, called a row key and a + * column key, with a single value. A table may be sparse, with only a small + * fraction of row key / column key pairs possessing a corresponding value. + * + *

The mappings corresponding to a given row key may be viewed as a {@link + * Map} whose keys are the columns. The reverse is also available, associating a + * column with a row key / value map. Note that, in some implementations, data + * access by column key may have fewer supported operations or worse performance + * than data access by row key. + * + *

The methods returning collections or maps always return views of the + * underlying table. Updating the table can change the contents of those + * collections, and updating the collections will change the table. + * + *

All methods that modify the table are optional, and the views returned by + * the table may or may not be modifiable. When modification isn't supported, + * those methods will throw an {@link UnsupportedOperationException}. + * + *

See the Guava User Guide article on + * {@code Table}. + * + * @author Jared Levy + * @param the type of the table row keys + * @param the type of the table column keys + * @param the type of the mapped values + * @since 7.0 + */ +@GwtCompatible +public interface Table { + // TODO(jlevy): Consider adding methods similar to ConcurrentMap methods. + + // Accessors + + /** + * Returns {@code true} if the table contains a mapping with the specified + * row and column keys. + * + * @param rowKey key of row to search for + * @param columnKey key of column to search for + */ + boolean contains(@Nullable Object rowKey, @Nullable Object columnKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified + * row key. + * + * @param rowKey key of row to search for + */ + boolean containsRow(@Nullable Object rowKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified + * column. + * + * @param columnKey key of column to search for + */ + boolean containsColumn(@Nullable Object columnKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified + * value. + * + * @param value value to search for + */ + boolean containsValue(@Nullable Object value); + + /** + * Returns the value corresponding to the given row and column keys, or + * {@code null} if no such mapping exists. + * + * @param rowKey key of row to search for + * @param columnKey key of column to search for + */ + V get(@Nullable Object rowKey, @Nullable Object columnKey); + + /** Returns {@code true} if the table contains no mappings. */ + boolean isEmpty(); + + /** + * Returns the number of row key / column key / value mappings in the table. + */ + int size(); + + /** + * Compares the specified object with this table for equality. Two tables are + * equal when their cell views, as returned by {@link #cellSet}, are equal. + */ + @Override + boolean equals(@Nullable Object obj); + + /** + * Returns the hash code for this table. The hash code of a table is defined + * as the hash code of its cell view, as returned by {@link #cellSet}. + */ + @Override + int hashCode(); + + // Mutators + + /** Removes all mappings from the table. */ + void clear(); + + /** + * Associates the specified value with the specified keys. If the table + * already contained a mapping for those keys, the old value is replaced with + * the specified value. + * + * @param rowKey row key that the value should be associated with + * @param columnKey column key that the value should be associated with + * @param value value to be associated with the specified keys + * @return the value previously associated with the keys, or {@code null} if + * no mapping existed for the keys + */ + V put(R rowKey, C columnKey, V value); + + /** + * Copies all mappings from the specified table to this table. The effect is + * equivalent to calling {@link #put} with each row key / column key / value + * mapping in {@code table}. + * + * @param table the table to add to this table + */ + void putAll(Table table); + + /** + * Removes the mapping, if any, associated with the given keys. + * + * @param rowKey row key of mapping to be removed + * @param columnKey column key of mapping to be removed + * @return the value previously associated with the keys, or {@code null} if + * no such value existed + */ + V remove(@Nullable Object rowKey, @Nullable Object columnKey); + + // Views + + /** + * Returns a view of all mappings that have the given row key. For each row + * key / column key / value mapping in the table with that row key, the + * returned map associates the column key with the value. If no mappings in + * the table have the provided row key, an empty map is returned. + * + *

Changes to the returned map will update the underlying table, and vice + * versa. + * + * @param rowKey key of row to search for in the table + * @return the corresponding map from column keys to values + */ + Map row(R rowKey); + + /** + * Returns a view of all mappings that have the given column key. For each row + * key / column key / value mapping in the table with that column key, the + * returned map associates the row key with the value. If no mappings in the + * table have the provided column key, an empty map is returned. + * + *

Changes to the returned map will update the underlying table, and vice + * versa. + * + * @param columnKey key of column to search for in the table + * @return the corresponding map from row keys to values + */ + Map column(C columnKey); + + /** + * Returns a set of all row key / column key / value triplets. Changes to the + * returned set will update the underlying table, and vice versa. The cell set + * does not support the {@code add} or {@code addAll} methods. + * + * @return set of table cells consisting of row key / column key / value + * triplets + */ + Set> cellSet(); + + /** + * Returns a set of row keys that have one or more values in the table. + * Changes to the set will update the underlying table, and vice versa. + * + * @return set of row keys + */ + Set rowKeySet(); + + /** + * Returns a set of column keys that have one or more values in the table. + * Changes to the set will update the underlying table, and vice versa. + * + * @return set of column keys + */ + Set columnKeySet(); + + /** + * Returns a collection of all values, which may contain duplicates. Changes + * to the returned collection will update the underlying table, and vice + * versa. + * + * @return collection of values + */ + Collection values(); + + /** + * Returns a view that associates each row key with the corresponding map from + * column keys to values. Changes to the returned map will update this table. + * The returned map does not support {@code put()} or {@code putAll()}, or + * {@code setValue()} on its entries. + * + *

In contrast, the maps returned by {@code rowMap().get()} have the same + * behavior as those returned by {@link #row}. Those maps may support {@code + * setValue()}, {@code put()}, and {@code putAll()}. + * + * @return a map view from each row key to a secondary map from column keys to + * values + */ + Map> rowMap(); + + /** + * Returns a view that associates each column key with the corresponding map + * from row keys to values. Changes to the returned map will update this + * table. The returned map does not support {@code put()} or {@code putAll()}, + * or {@code setValue()} on its entries. + * + *

In contrast, the maps returned by {@code columnMap().get()} have the + * same behavior as those returned by {@link #column}. Those maps may support + * {@code setValue()}, {@code put()}, and {@code putAll()}. + * + * @return a map view from each column key to a secondary map from row keys to + * values + */ + Map> columnMap(); + + /** + * Row key / column key / value triplet corresponding to a mapping in a table. + * + * @since 7.0 + */ + interface Cell { + /** + * Returns the row key of this cell. + */ + R getRowKey(); + + /** + * Returns the column key of this cell. + */ + C getColumnKey(); + + /** + * Returns the value of this cell. + */ + V getValue(); + + /** + * Compares the specified object with this cell for equality. Two cells are + * equal when they have equal row keys, column keys, and values. + */ + @Override + boolean equals(@Nullable Object obj); + + /** + * Returns the hash code of this cell. + * + *

The hash code of a table cell is equal to {@link + * Objects#hashCode}{@code (e.getRowKey(), e.getColumnKey(), e.getValue())}. + */ + @Override + int hashCode(); + } +} diff --git a/guava/src/com/google/common/collect/Tables.java b/guava/src/com/google/common/collect/Tables.java new file mode 100644 index 0000000..028aba3 --- /dev/null +++ b/guava/src/com/google/common/collect/Tables.java @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Supplier; +import com.google.common.collect.Collections2.TransformedCollection; +import com.google.common.collect.Table.Cell; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Provides static methods that involve a {@code Table}. + * + *

See the Guava User Guide article on + * {@code Tables}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 7.0 + */ +@GwtCompatible +public final class Tables { + private Tables() {} + + /** + * Returns an immutable cell with the specified row key, column key, and + * value. + * + *

The returned cell is serializable. + * + * @param rowKey the row key to be associated with the returned cell + * @param columnKey the column key to be associated with the returned cell + * @param value the value to be associated with the returned cell + */ + public static Cell immutableCell( + @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + return new ImmutableCell(rowKey, columnKey, value); + } + + static final class ImmutableCell + extends AbstractCell implements Serializable { + private final R rowKey; + private final C columnKey; + private final V value; + + ImmutableCell( + @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + @Override + public C getColumnKey() { + return columnKey; + } + @Override + public V getValue() { + return value; + } + + private static final long serialVersionUID = 0; + } + + abstract static class AbstractCell implements Cell { + // needed for serialization + AbstractCell() {} + + @Override public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return Objects.equal(getRowKey(), other.getRowKey()) + && Objects.equal(getColumnKey(), other.getColumnKey()) + && Objects.equal(getValue(), other.getValue()); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); + } + + @Override public String toString() { + return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); + } + } + + /** + * Creates a transposed view of a given table that flips its row and column + * keys. In other words, calling {@code get(columnKey, rowKey)} on the + * generated table always returns the same value as calling {@code + * get(rowKey, columnKey)} on the original table. Updating the original table + * changes the contents of the transposed table and vice versa. + * + *

The returned table supports update operations as long as the input table + * supports the analogous operation with swapped rows and columns. For + * example, in a {@link HashBasedTable} instance, {@code + * rowKeySet().iterator()} supports {@code remove()} but {@code + * columnKeySet().iterator()} doesn't. With a transposed {@link + * HashBasedTable}, it's the other way around. + */ + public static Table transpose(Table table) { + return (table instanceof TransposeTable) + ? ((TransposeTable) table).original + : new TransposeTable(table); + } + + private static class TransposeTable implements Table { + final Table original; + + TransposeTable(Table original) { + this.original = checkNotNull(original); + } + + @Override + public void clear() { + original.clear(); + } + + @Override + public Map column(R columnKey) { + return original.row(columnKey); + } + + @Override + public Set columnKeySet() { + return original.rowKeySet(); + } + + @Override + public Map> columnMap() { + return original.rowMap(); + } + + @Override + public boolean contains( + @Nullable Object rowKey, @Nullable Object columnKey) { + return original.contains(columnKey, rowKey); + } + + @Override + public boolean containsColumn(@Nullable Object columnKey) { + return original.containsRow(columnKey); + } + + @Override + public boolean containsRow(@Nullable Object rowKey) { + return original.containsColumn(rowKey); + } + + @Override + public boolean containsValue(@Nullable Object value) { + return original.containsValue(value); + } + + @Override + public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + return original.get(columnKey, rowKey); + } + + @Override + public boolean isEmpty() { + return original.isEmpty(); + } + + @Override + public V put(C rowKey, R columnKey, V value) { + return original.put(columnKey, rowKey, value); + } + + @Override + public void putAll(Table table) { + original.putAll(transpose(table)); + } + + @Override + public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { + return original.remove(columnKey, rowKey); + } + + @Override + public Map row(C rowKey) { + return original.column(rowKey); + } + + @Override + public Set rowKeySet() { + return original.columnKeySet(); + } + + @Override + public Map> rowMap() { + return original.columnMap(); + } + + @Override + public int size() { + return original.size(); + } + + @Override + public Collection values() { + return original.values(); + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Table) { + Table other = (Table) obj; + return cellSet().equals(other.cellSet()); + } + return false; + } + + @Override public int hashCode() { + return cellSet().hashCode(); + } + + @Override public String toString() { + return rowMap().toString(); + } + + // Will cast TRANSPOSE_CELL to a type that always succeeds + private static final Function, Cell> TRANSPOSE_CELL = + new Function, Cell>() { + @Override + public Cell apply(Cell cell) { + return immutableCell( + cell.getColumnKey(), cell.getRowKey(), cell.getValue()); + } + }; + + CellSet cellSet; + + @Override + public Set> cellSet() { + CellSet result = cellSet; + return (result == null) ? cellSet = new CellSet() : result; + } + + class CellSet extends TransformedCollection, Cell> + implements Set> { + // Casting TRANSPOSE_CELL to a type that always succeeds + @SuppressWarnings("unchecked") + CellSet() { + super(original.cellSet(), (Function) TRANSPOSE_CELL); + } + + @Override public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Set)) { + return false; + } + Set os = (Set) obj; + if (os.size() != size()) { + return false; + } + return containsAll(os); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override public boolean contains(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + return original.cellSet().contains(immutableCell( + cell.getColumnKey(), cell.getRowKey(), cell.getValue())); + } + return false; + } + + @Override public boolean remove(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + return original.cellSet().remove(immutableCell( + cell.getColumnKey(), cell.getRowKey(), cell.getValue())); + } + return false; + } + } + } + + /** + * Creates a table that uses the specified backing map and factory. It can + * generate a table based on arbitrary {@link Map} classes. + * + *

The {@code factory}-generated and {@code backingMap} classes determine + * the table iteration order. However, the table's {@code row()} method + * returns instances of a different class than {@code factory.get()} does. + * + *

Call this method only when the simpler factory methods in classes like + * {@link HashBasedTable} and {@link TreeBasedTable} won't suffice. + * + *

The views returned by the {@code Table} methods {@link Table#column}, + * {@link Table#columnKeySet}, and {@link Table#columnMap} have iterators that + * don't support {@code remove()}. Otherwise, all optional operations are + * supported. Null row keys, columns keys, and values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because + * the data is stored in a {@code Map>}. A method call like + * {@code column(columnKey).get(rowKey)} still runs quickly, since the row key + * is provided. However, {@code column(columnKey).size()} takes longer, since + * an iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads + * access this table concurrently and one of the threads modifies the table, + * it must be synchronized externally. + * + *

The table is serializable if {@code backingMap}, {@code factory}, the + * maps generated by {@code factory}, and the table contents are all + * serializable. + * + *

Note: the table assumes complete ownership over of {@code backingMap} + * and the maps returned by {@code factory}. Those objects should not be + * manually updated and they should not use soft, weak, or phantom references. + * + * @param backingMap place to store the mapping from each row key to its + * corresponding column key / value map + * @param factory supplier of new, empty maps that will each hold all column + * key / value mappings for a given row key + * @throws IllegalArgumentException if {@code backingMap} is not empty + * @since 10.0 + */ + @Beta + public static Table newCustomTable( + Map> backingMap, Supplier> factory) { + checkArgument(backingMap.isEmpty()); + checkNotNull(factory); + // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? + return new StandardTable(backingMap, factory); + } + + /** + * Returns a view of a table where each value is transformed by a function. + * All other properties of the table, such as iteration order, are left + * intact. + * + *

Changes in the underlying table are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying table. + * + *

It's acceptable for the underlying table to contain null keys, and even + * null values provided that the function is capable of accepting null input. + * The transformed table might contain null values, if the function sometimes + * gives a null result. + * + *

The returned table is not thread-safe or serializable, even if the + * underlying table is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned table to be a view, but it means that the function will be + * applied many times for bulk operations like {@link Table#containsValue} and + * {@code Table.toString()}. For this to perform well, {@code function} should + * be fast. To avoid lazy evaluation when the returned table doesn't need to + * be a view, copy the returned table into a new table of your choosing. + * + * @since 10.0 + */ + @Beta + public static Table transformValues( + Table fromTable, Function function) { + return new TransformedTable(fromTable, function); + } + + private static class TransformedTable + implements Table { + final Table fromTable; + final Function function; + + TransformedTable( + Table fromTable, Function function) { + this.fromTable = checkNotNull(fromTable); + this.function = checkNotNull(function); + } + + @Override public boolean contains(Object rowKey, Object columnKey) { + return fromTable.contains(rowKey, columnKey); + } + + @Override public boolean containsRow(Object rowKey) { + return fromTable.containsRow(rowKey); + } + + @Override public boolean containsColumn(Object columnKey) { + return fromTable.containsColumn(columnKey); + } + + @Override public boolean containsValue(Object value) { + return values().contains(value); + } + + @Override public V2 get(Object rowKey, Object columnKey) { + // The function is passed a null input only when the table contains a null + // value. + return contains(rowKey, columnKey) + ? function.apply(fromTable.get(rowKey, columnKey)) : null; + } + + @Override public boolean isEmpty() { + return fromTable.isEmpty(); + } + + @Override public int size() { + return fromTable.size(); + } + + @Override public void clear() { + fromTable.clear(); + } + + @Override public V2 put(R rowKey, C columnKey, V2 value) { + throw new UnsupportedOperationException(); + } + + @Override public void putAll( + Table table) { + throw new UnsupportedOperationException(); + } + + @Override public V2 remove(Object rowKey, Object columnKey) { + return contains(rowKey, columnKey) + ? function.apply(fromTable.remove(rowKey, columnKey)) : null; + } + + @Override public Map row(R rowKey) { + return Maps.transformValues(fromTable.row(rowKey), function); + } + + @Override public Map column(C columnKey) { + return Maps.transformValues(fromTable.column(columnKey), function); + } + + Function, Cell> cellFunction() { + return new Function, Cell>() { + @Override public Cell apply(Cell cell) { + return immutableCell( + cell.getRowKey(), cell.getColumnKey(), + function.apply(cell.getValue())); + } + }; + } + + class CellSet extends TransformedCollection, Cell> + implements Set> { + CellSet() { + super(fromTable.cellSet(), cellFunction()); + } + @Override public boolean equals(Object obj) { + return Sets.equalsImpl(this, obj); + } + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + @Override public boolean contains(Object obj) { + if (obj instanceof Cell) { + Cell cell = (Cell) obj; + if (!Objects.equal( + cell.getValue(), get(cell.getRowKey(), cell.getColumnKey()))) { + return false; + } + return cell.getValue() != null + || fromTable.contains(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + @Override public boolean remove(Object obj) { + if (contains(obj)) { + Cell cell = (Cell) obj; + fromTable.remove(cell.getRowKey(), cell.getColumnKey()); + return true; + } + return false; + } + } + + CellSet cellSet; + + @Override public Set> cellSet() { + return (cellSet == null) ? cellSet = new CellSet() : cellSet; + } + + @Override public Set rowKeySet() { + return fromTable.rowKeySet(); + } + + @Override public Set columnKeySet() { + return fromTable.columnKeySet(); + } + + Collection values; + + @Override public Collection values() { + return (values == null) + ? values = Collections2.transform(fromTable.values(), function) + : values; + } + + Map> createRowMap() { + Function, Map> rowFunction = + new Function, Map>() { + @Override public Map apply(Map row) { + return Maps.transformValues(row, function); + } + }; + return Maps.transformValues(fromTable.rowMap(), rowFunction); + } + + Map> rowMap; + + @Override public Map> rowMap() { + return (rowMap == null) ? rowMap = createRowMap() : rowMap; + } + + Map> createColumnMap() { + Function, Map> columnFunction = + new Function, Map>() { + @Override public Map apply(Map column) { + return Maps.transformValues(column, function); + } + }; + return Maps.transformValues(fromTable.columnMap(), columnFunction); + } + + Map> columnMap; + + @Override public Map> columnMap() { + return (columnMap == null) ? columnMap = createColumnMap() : columnMap; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Table) { + Table other = (Table) obj; + return cellSet().equals(other.cellSet()); + } + return false; + } + + @Override public int hashCode() { + return cellSet().hashCode(); + } + + @Override public String toString() { + return rowMap().toString(); + } + } + + /** + * Returns an unmodifiable view of the specified table. This method allows modules to provide + * users with "read-only" access to internal tables. Query operations on the returned table + * "read through" to the specified table, and attempts to modify the returned table, whether + * direct or via its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned table will be serializable if the specified table is serializable. + * + *

Consider using an {@link ImmutableTable}, which is guaranteed never to change. + * + * @param table + * the table for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified table + * @since 11.0 + */ + public static Table unmodifiableTable( + Table table) { + return new UnmodifiableTable(table); + } + + private static class UnmodifiableTable + extends ForwardingTable implements Serializable { + final Table delegate; + + UnmodifiableTable(Table delegate) { + this.delegate = checkNotNull(delegate); + } + + @SuppressWarnings("unchecked") // safe, covariant cast + @Override + protected Table delegate() { + return (Table) delegate; + } + + @Override + public Set> cellSet() { + return Collections.unmodifiableSet(super.cellSet()); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Map column(@Nullable C columnKey) { + return Collections.unmodifiableMap(super.column(columnKey)); + } + + @Override + public Set columnKeySet() { + return Collections.unmodifiableSet(super.columnKeySet()); + } + + @Override + public Map> columnMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); + } + + @Override + public V put(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Table table) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map row(@Nullable R rowKey) { + return Collections.unmodifiableMap(super.row(rowKey)); + } + + @Override + public Set rowKeySet() { + return Collections.unmodifiableSet(super.rowKeySet()); + } + + @Override + public Map> rowMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(super.values()); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to + * provide users with "read-only" access to internal tables. Query operations on the returned + * table "read through" to the specified table, and attemps to modify the returned table, whether + * direct or via its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned table will be serializable if the specified table is serializable. + * + * @param table the row-sorted table for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified table + * @since 11.0 + */ + @Beta + public static RowSortedTable unmodifiableRowSortedTable( + RowSortedTable table) { + /* + * It's not ? extends R, because it's technically not covariant in R. Specifically, + * table.rowMap().comparator() could return a comparator that only works for the ? extends R. + * Collections.unmodifiableSortedMap makes the same distinction. + */ + return new UnmodifiableRowSortedMap(table); + } + + static final class UnmodifiableRowSortedMap extends UnmodifiableTable + implements RowSortedTable { + + public UnmodifiableRowSortedMap(RowSortedTable delegate) { + super(delegate); + } + + @Override + protected RowSortedTable delegate() { + return (RowSortedTable) super.delegate(); + } + + @Override + public SortedMap> rowMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); + } + + @Override + public SortedSet rowKeySet() { + return Collections.unmodifiableSortedSet(delegate().rowKeySet()); + } + + private static final long serialVersionUID = 0; + } + + @SuppressWarnings("unchecked") + private static Function, Map> unmodifiableWrapper() { + return (Function) UNMODIFIABLE_WRAPPER; + } + + private static final Function, ? extends Map> UNMODIFIABLE_WRAPPER = + new Function, Map>() { + @Override + public Map apply(Map input) { + return Collections.unmodifiableMap(input); + } + }; +} diff --git a/guava/src/com/google/common/collect/TransformedImmutableSet.java b/guava/src/com/google/common/collect/TransformedImmutableSet.java new file mode 100644 index 0000000..a6a59c9 --- /dev/null +++ b/guava/src/com/google/common/collect/TransformedImmutableSet.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.util.Iterator; + +/** + * An {@code ImmutableSet} whose elements are derived by transforming another collection's elements, + * useful for {@code ImmutableMap.keySet()}. + * + * @author Jesse Wilson + */ +@GwtCompatible(emulated = true) +abstract class TransformedImmutableSet extends ImmutableSet { + /* + * TODO(cpovirk): using an abstract source() method instead of a field could simplify + * ImmutableMapKeySet, which currently has to pass in entrySet() manually + */ + final ImmutableCollection source; + final int hashCode; + + TransformedImmutableSet(ImmutableCollection source) { + this.source = source; + this.hashCode = Sets.hashCodeImpl(this); + } + + TransformedImmutableSet(ImmutableCollection source, int hashCode) { + this.source = source; + this.hashCode = hashCode; + } + + abstract E transform(D element); + + @Override + public int size() { + return source.size(); + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + final Iterator backingIterator = source.iterator(); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public E next() { + return transform(backingIterator.next()); + } + }; + } + + @Override public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public final int hashCode() { + return hashCode; + } + + @GwtIncompatible("unused") + @Override boolean isHashCodeFast() { + return true; + } +} diff --git a/guava/src/com/google/common/collect/TransformedIterator.java b/guava/src/com/google/common/collect/TransformedIterator.java new file mode 100644 index 0000000..c082d7d --- /dev/null +++ b/guava/src/com/google/common/collect/TransformedIterator.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator that transforms a backing iterator; for internal use. This avoids + * the object overhead of constructing a {@link Function} for internal methods. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class TransformedIterator implements Iterator { + final Iterator backingIterator; + + TransformedIterator(Iterator backingIterator) { + this.backingIterator = checkNotNull(backingIterator); + } + + abstract T transform(F from); + + @Override + public final boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public final T next() { + return transform(backingIterator.next()); + } + + @Override + public final void remove() { + backingIterator.remove(); + } +} diff --git a/guava/src/com/google/common/collect/TransformedListIterator.java b/guava/src/com/google/common/collect/TransformedListIterator.java new file mode 100644 index 0000000..c743030 --- /dev/null +++ b/guava/src/com/google/common/collect/TransformedListIterator.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; + +import java.util.ListIterator; + +/** + * An iterator that transforms a backing list iterator; for internal use. This + * avoids the object overhead of constructing a {@link Function} for internal + * methods. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class TransformedListIterator extends TransformedIterator + implements ListIterator { + TransformedListIterator(ListIterator backingIterator) { + super(backingIterator); + } + + private ListIterator backingIterator() { + return Iterators.cast(backingIterator); + } + + @Override + public final boolean hasPrevious() { + return backingIterator().hasPrevious(); + } + + @Override + public final T previous() { + return transform(backingIterator().previous()); + } + + @Override + public final int nextIndex() { + return backingIterator().nextIndex(); + } + + @Override + public final int previousIndex() { + return backingIterator().previousIndex(); + } + + @Override + public void set(T element) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(T element) { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/collect/TreeBasedTable.java b/guava/src/com/google/common/collect/TreeBasedTable.java new file mode 100644 index 0000000..2ead64e --- /dev/null +++ b/guava/src/com/google/common/collect/TreeBasedTable.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Supplier; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * Implementation of {@code Table} whose row keys and column keys are ordered + * by their natural ordering or by supplied comparators. When constructing a + * {@code TreeBasedTable}, you may provide comparators for the row keys and + * the column keys, or you may use natural ordering for both. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link + * #rowMap} method returns a {@link SortedMap}, instead of the {@link Set} and + * {@link Map} specified by the {@link Table} interface. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link + * #columnMap()} have iterators that don't support {@code remove()}. Otherwise, + * all optional operations are supported. Null row keys, columns keys, and + * values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because + * the data is stored in a {@code Map>}. A method call like {@code + * column(columnKey).get(rowKey)} still runs quickly, since the row key is + * provided. However, {@code column(columnKey).size()} takes longer, since an + * iteration across all row keys occurs. + * + *

Because a {@code TreeBasedTable} has unique sorted values for a given + * row, both {@code row(rowKey)} and {@code rowMap().get(rowKey)} are {@link + * SortedMap} instances, instead of the {@link Map} specified in the {@link + * Table} interface. + * + *

Note that this implementation is not synchronized. If multiple threads + * access this table concurrently and one of the threads modifies the table, it + * must be synchronized externally. + * + *

See the Guava User Guide article on + * {@code Table}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 7.0 + */ +@GwtCompatible(serializable = true) +@Beta +public class TreeBasedTable extends StandardRowSortedTable { + private final Comparator columnComparator; + + private static class Factory + implements Supplier>, Serializable { + final Comparator comparator; + Factory(Comparator comparator) { + this.comparator = comparator; + } + @Override + public TreeMap get() { + return new TreeMap(comparator); + } + private static final long serialVersionUID = 0; + } + + /** + * Creates an empty {@code TreeBasedTable} that uses the natural orderings + * of both row and column keys. + * + *

The method signature specifies {@code R extends Comparable} with a raw + * {@link Comparable}, instead of {@code R extends Comparable}, + * and the same for {@code C}. That's necessary to support classes defined + * without generics. + */ + public static + TreeBasedTable create() { + return new TreeBasedTable(Ordering.natural(), + Ordering.natural()); + } + + /** + * Creates an empty {@code TreeBasedTable} that is ordered by the specified + * comparators. + * + * @param rowComparator the comparator that orders the row keys + * @param columnComparator the comparator that orders the column keys + */ + public static TreeBasedTable create( + Comparator rowComparator, + Comparator columnComparator) { + checkNotNull(rowComparator); + checkNotNull(columnComparator); + return new TreeBasedTable(rowComparator, columnComparator); + } + + /** + * Creates a {@code TreeBasedTable} with the same mappings and sort order + * as the specified {@code TreeBasedTable}. + */ + public static TreeBasedTable create( + TreeBasedTable table) { + TreeBasedTable result + = new TreeBasedTable( + table.rowComparator(), table.columnComparator()); + result.putAll(table); + return result; + } + + TreeBasedTable(Comparator rowComparator, + Comparator columnComparator) { + super(new TreeMap>(rowComparator), + new Factory(columnComparator)); + this.columnComparator = columnComparator; + } + + // TODO(jlevy): Move to StandardRowSortedTable? + + /** + * Returns the comparator that orders the rows. With natural ordering, + * {@link Ordering#natural()} is returned. + */ + public Comparator rowComparator() { + return rowKeySet().comparator(); + } + + /** + * Returns the comparator that orders the columns. With natural ordering, + * {@link Ordering#natural()} is returned. + */ + public Comparator columnComparator() { + return columnComparator; + } + + // TODO(user): make column return a SortedMap + + /** + * {@inheritDoc} + * + *

Because a {@code TreeBasedTable} has unique sorted values for a given + * row, this method returns a {@link SortedMap}, instead of the {@link Map} + * specified in the {@link Table} interface. + * @since 10.0 + * (mostly source-compatible since 7.0) + */ + @Override + public SortedMap row(R rowKey) { + return new TreeRow(rowKey); + } + + private class TreeRow extends Row implements SortedMap { + @Nullable final C lowerBound; + @Nullable final C upperBound; + + TreeRow(R rowKey) { + this(rowKey, null, null); + } + + TreeRow(R rowKey, @Nullable C lowerBound, @Nullable C upperBound) { + super(rowKey); + this.lowerBound = lowerBound; + this.upperBound = upperBound; + checkArgument(lowerBound == null || upperBound == null + || compare(lowerBound, upperBound) <= 0); + } + + @Override public Comparator comparator() { + return columnComparator(); + } + + int compare(Object a, Object b) { + // pretend we can compare anything + @SuppressWarnings({"rawtypes", "unchecked"}) + Comparator cmp = (Comparator) comparator(); + return cmp.compare(a, b); + } + + boolean rangeContains(@Nullable Object o) { + return o != null && (lowerBound == null || compare(lowerBound, o) <= 0) + && (upperBound == null || compare(upperBound, o) > 0); + } + + @Override public SortedMap subMap(C fromKey, C toKey) { + checkArgument(rangeContains(checkNotNull(fromKey)) + && rangeContains(checkNotNull(toKey))); + return new TreeRow(rowKey, fromKey, toKey); + } + + @Override public SortedMap headMap(C toKey) { + checkArgument(rangeContains(checkNotNull(toKey))); + return new TreeRow(rowKey, lowerBound, toKey); + } + + @Override public SortedMap tailMap(C fromKey) { + checkArgument(rangeContains(checkNotNull(fromKey))); + return new TreeRow(rowKey, fromKey, upperBound); + } + + @Override public C firstKey() { + SortedMap backing = backingRowMap(); + if (backing == null) { + throw new NoSuchElementException(); + } + return backingRowMap().firstKey(); + } + + @Override public C lastKey() { + SortedMap backing = backingRowMap(); + if (backing == null) { + throw new NoSuchElementException(); + } + return backingRowMap().lastKey(); + } + + transient SortedMap wholeRow; + + /* + * If the row was previously empty, we check if there's a new row here every + * time we're queried. + */ + SortedMap wholeRow() { + if (wholeRow == null + || (wholeRow.isEmpty() && backingMap.containsKey(rowKey))) { + wholeRow = (SortedMap) backingMap.get(rowKey); + } + return wholeRow; + } + + @Override + SortedMap backingRowMap() { + return (SortedMap) super.backingRowMap(); + } + + @Override + SortedMap computeBackingRowMap() { + SortedMap map = wholeRow(); + if (map != null) { + if (lowerBound != null) { + map = map.tailMap(lowerBound); + } + if (upperBound != null) { + map = map.headMap(upperBound); + } + return map; + } + return null; + } + + @Override + void maintainEmptyInvariant() { + if (wholeRow() != null && wholeRow.isEmpty()) { + backingMap.remove(rowKey); + wholeRow = null; + backingRowMap = null; + } + } + + @Override public boolean containsKey(Object key) { + return rangeContains(key) && super.containsKey(key); + } + + @Override public V put(C key, V value) { + checkArgument(rangeContains(checkNotNull(key))); + return super.put(key, value); + } + } + + // rowKeySet() and rowMap() are defined here so they appear in the Javadoc. + + @Override public SortedSet rowKeySet() { + return super.rowKeySet(); + } + + @Override public SortedMap> rowMap() { + return super.rowMap(); + } + + // Overriding so NullPointerTester test passes. + + @Override public boolean contains( + @Nullable Object rowKey, @Nullable Object columnKey) { + return super.contains(rowKey, columnKey); + } + + @Override public boolean containsColumn(@Nullable Object columnKey) { + return super.containsColumn(columnKey); + } + + @Override public boolean containsRow(@Nullable Object rowKey) { + return super.containsRow(rowKey); + } + + @Override public boolean containsValue(@Nullable Object value) { + return super.containsValue(value); + } + + @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { + return super.get(rowKey, columnKey); + } + + @Override public boolean equals(@Nullable Object obj) { + return super.equals(obj); + } + + @Override public V remove( + @Nullable Object rowKey, @Nullable Object columnKey) { + return super.remove(rowKey, columnKey); + } + + /** + * Overridden column iterator to return columns values in globally sorted + * order. + */ + @Override + Iterator createColumnKeyIterator() { + final Comparator comparator = columnComparator(); + + final Iterator merged = + Iterators.mergeSorted(Iterables.transform(backingMap.values(), + new Function, Iterator>() { + @Override + public Iterator apply(Map input) { + return input.keySet().iterator(); + } + }), comparator); + + return new AbstractIterator() { + C lastValue; + + @Override + protected C computeNext() { + while (merged.hasNext()) { + C next = merged.next(); + boolean duplicate = + lastValue != null && comparator.compare(next, lastValue) == 0; + + // Keep looping till we find a non-duplicate value. + if (!duplicate) { + lastValue = next; + return lastValue; + } + } + + lastValue = null; // clear reference to unused data + return endOfData(); + } + }; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/TreeMultimap.java b/guava/src/com/google/common/collect/TreeMultimap.java new file mode 100644 index 0000000..3bd49ae --- /dev/null +++ b/guava/src/com/google/common/collect/TreeMultimap.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Implementation of {@code Multimap} whose keys and values are ordered by + * their natural ordering or by supplied comparators. In all cases, this + * implementation uses {@link Comparable#compareTo} or {@link + * Comparator#compare} instead of {@link Object#equals} to determine + * equivalence of instances. + * + *

Warning: The comparators or comparables used must be consistent + * with equals as explained by the {@link Comparable} class specification. + * Otherwise, the resulting multiset will violate the general contract of {@link + * SetMultimap}, which it is specified in terms of {@link Object#equals}. + * + *

The collections returned by {@code keySet} and {@code asMap} iterate + * through the keys according to the key comparator ordering or the natural + * ordering of the keys. Similarly, {@code get}, {@code removeAll}, and {@code + * replaceValues} return collections that iterate through the values according + * to the value comparator ordering or the natural ordering of the values. The + * collections generated by {@code entries}, {@code keys}, and {@code values} + * iterate across the keys according to the above key ordering, and for each + * key they iterate across the values according to the value ordering. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Null keys and values are permitted (provided, of course, that the + * respective comparators support them). All optional multimap methods are + * supported, and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSortedSetMultimap}. + * + *

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(serializable = true, emulated = true) +public class TreeMultimap extends AbstractSortedSetMultimap { + private transient Comparator keyComparator; + private transient Comparator valueComparator; + + /** + * Creates an empty {@code TreeMultimap} ordered by the natural ordering of + * its keys and values. + */ + public static + TreeMultimap create() { + return new TreeMultimap(Ordering.natural(), Ordering.natural()); + } + + /** + * Creates an empty {@code TreeMultimap} instance using explicit comparators. + * Neither comparator may be null; use {@link Ordering#natural()} to specify + * natural order. + * + * @param keyComparator the comparator that determines the key ordering + * @param valueComparator the comparator that determines the value ordering + */ + public static TreeMultimap create( + Comparator keyComparator, + Comparator valueComparator) { + return new TreeMultimap(checkNotNull(keyComparator), + checkNotNull(valueComparator)); + } + + /** + * Constructs a {@code TreeMultimap}, ordered by the natural ordering of its + * keys and values, with the same mappings as the specified multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static + TreeMultimap create(Multimap multimap) { + return new TreeMultimap(Ordering.natural(), Ordering.natural(), + multimap); + } + + TreeMultimap(Comparator keyComparator, + Comparator valueComparator) { + super(new TreeMap>(keyComparator)); + this.keyComparator = keyComparator; + this.valueComparator = valueComparator; + } + + private TreeMultimap(Comparator keyComparator, + Comparator valueComparator, + Multimap multimap) { + this(keyComparator, valueComparator); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code TreeSet} for a collection of values for one key. + * + * @return a new {@code TreeSet} containing a collection of values for one + * key + */ + @Override SortedSet createCollection() { + return new TreeSet(valueComparator); + } + + /** + * Returns the comparator that orders the multimap keys. + */ + public Comparator keyComparator() { + return keyComparator; + } + + @Override + public Comparator valueComparator() { + return valueComparator; + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method + * returns a {@link SortedSet}, instead of the {@link java.util.Set} specified + * in the {@link Multimap} interface. + */ + @Override public SortedSet keySet() { + return (SortedSet) super.keySet(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method + * returns a {@link SortedMap}, instead of the {@link java.util.Map} specified + * in the {@link Multimap} interface. + */ + @Override public SortedMap> asMap() { + return (SortedMap>) super.asMap(); + } + + /** + * @serialData key comparator, value comparator, number of distinct keys, and + * then for each distinct key: the key, number of values for that key, and + * key values + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyComparator()); + stream.writeObject(valueComparator()); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyComparator = checkNotNull((Comparator) stream.readObject()); + valueComparator = checkNotNull((Comparator) stream.readObject()); + setMap(new TreeMap>(keyComparator)); + Serialization.populateMultimap(this, stream); + } + + @GwtIncompatible("not needed in emulated source") + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/TreeMultiset.java b/guava/src/com/google/common/collect/TreeMultiset.java new file mode 100644 index 0000000..6876cd0 --- /dev/null +++ b/guava/src/com/google/common/collect/TreeMultiset.java @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * A multiset which maintains the ordering of its elements, according to either their natural order + * or an explicit {@link Comparator}. In all cases, this implementation uses + * {@link Comparable#compareTo} or {@link Comparator#compare} instead of {@link Object#equals} to + * determine equivalence of instances. + * + *

Warning: The comparison must be consistent with equals as explained by the + * {@link Comparable} class specification. Otherwise, the resulting multiset will violate the + * {@link java.util.Collection} contract, which is specified in terms of {@link Object#equals}. + * + *

See the Guava User Guide article on + * {@code Multiset}. + * + * @author Louis Wasserman + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible(emulated = true) +public final class TreeMultiset extends AbstractSortedMultiset implements Serializable { + + /** + * Creates a new, empty multiset, sorted according to the elements' natural order. All elements + * inserted into the multiset must implement the {@code Comparable} interface. Furthermore, all + * such elements must be mutually comparable: {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and {@code e2} in the multiset. If the + * user attempts to add an element to the multiset that violates this constraint (for example, + * the user attempts to add a string element to a set whose elements are integers), the + * {@code add(Object)} call will throw a {@code ClassCastException}. + * + *

The type specification is {@code }, instead of the more specific + * {@code >}, to support classes defined without generics. + */ + public static TreeMultiset create() { + return new TreeMultiset(Ordering.natural()); + } + + /** + * Creates a new, empty multiset, sorted according to the specified comparator. All elements + * inserted into the multiset must be mutually comparable by the specified comparator: + * {@code comparator.compare(e1, + * e2)} must not throw a {@code ClassCastException} for any elements {@code e1} and {@code e2} in + * the multiset. If the user attempts to add an element to the multiset that violates this + * constraint, the {@code add(Object)} call will throw a {@code ClassCastException}. + * + * @param comparator + * the comparator that will be used to sort this multiset. A null value indicates that + * the elements' natural ordering should be used. + */ + @SuppressWarnings("unchecked") + public static TreeMultiset create(@Nullable Comparator comparator) { + return (comparator == null) + ? new TreeMultiset((Comparator) Ordering.natural()) + : new TreeMultiset(comparator); + } + + /** + * Creates an empty multiset containing the given initial elements, sorted according to the + * elements' natural order. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + *

The type specification is {@code }, instead of the more specific + * {@code >}, to support classes defined without generics. + */ + public static TreeMultiset create(Iterable elements) { + TreeMultiset multiset = create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + private final transient Reference> rootReference; + private final transient GeneralRange range; + private final transient AvlNode header; + + TreeMultiset(Reference> rootReference, GeneralRange range, AvlNode endLink) { + super(range.comparator()); + this.rootReference = rootReference; + this.range = range; + this.header = endLink; + } + + TreeMultiset(Comparator comparator) { + super(comparator); + this.range = GeneralRange.all(comparator); + this.header = new AvlNode(null, 1); + successor(header, header); + this.rootReference = new Reference>(); + } + + /** + * A function which can be summed across a subtree. + */ + private enum Aggregate { + SIZE { + @Override + int nodeAggregate(AvlNode node) { + return node.elemCount; + } + + @Override + long treeAggregate(@Nullable AvlNode root) { + return (root == null) ? 0 : root.totalCount; + } + }, + DISTINCT { + @Override + int nodeAggregate(AvlNode node) { + return 1; + } + + @Override + long treeAggregate(@Nullable AvlNode root) { + return (root == null) ? 0 : root.distinctElements; + } + }; + abstract int nodeAggregate(AvlNode node); + + abstract long treeAggregate(@Nullable AvlNode root); + } + + private long aggregateForEntries(Aggregate aggr) { + AvlNode root = rootReference.get(); + long total = aggr.treeAggregate(root); + if (range.hasLowerBound()) { + total -= aggregateBelowRange(aggr, root); + } + if (range.hasUpperBound()) { + total -= aggregateAboveRange(aggr, root); + } + return total; + } + + private long aggregateBelowRange(Aggregate aggr, @Nullable AvlNode node) { + if (node == null) { + return 0; + } + int cmp = comparator().compare(range.getLowerEndpoint(), node.elem); + if (cmp < 0) { + return aggregateBelowRange(aggr, node.left); + } else if (cmp == 0) { + switch (range.getLowerBoundType()) { + case OPEN: + return aggr.nodeAggregate(node) + aggr.treeAggregate(node.left); + case CLOSED: + return aggr.treeAggregate(node.left); + default: + throw new AssertionError(); + } + } else { + return aggr.treeAggregate(node.left) + aggr.nodeAggregate(node) + + aggregateBelowRange(aggr, node.right); + } + } + + private long aggregateAboveRange(Aggregate aggr, @Nullable AvlNode node) { + if (node == null) { + return 0; + } + int cmp = comparator().compare(range.getUpperEndpoint(), node.elem); + if (cmp > 0) { + return aggregateAboveRange(aggr, node.right); + } else if (cmp == 0) { + switch (range.getUpperBoundType()) { + case OPEN: + return aggr.nodeAggregate(node) + aggr.treeAggregate(node.right); + case CLOSED: + return aggr.treeAggregate(node.right); + default: + throw new AssertionError(); + } + } else { + return aggr.treeAggregate(node.right) + aggr.nodeAggregate(node) + + aggregateAboveRange(aggr, node.left); + } + } + + @Override + public int size() { + return Ints.saturatedCast(aggregateForEntries(Aggregate.SIZE)); + } + + @Override + int distinctElements() { + return Ints.saturatedCast(aggregateForEntries(Aggregate.DISTINCT)); + } + + @Override + public int count(@Nullable Object element) { + try { + @SuppressWarnings("unchecked") + E e = (E) element; + AvlNode root = rootReference.get(); + if (!range.contains(e) || root == null) { + return 0; + } + return root.count(comparator(), e); + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } + } + + @Override + public int add(@Nullable E element, int occurrences) { + checkArgument(occurrences >= 0, "occurrences must be >= 0 but was %s", occurrences); + if (occurrences == 0) { + return count(element); + } + checkArgument(range.contains(element)); + AvlNode root = rootReference.get(); + if (root == null) { + comparator().compare(element, element); + AvlNode newRoot = new AvlNode(element, occurrences); + successor(header, newRoot, header); + rootReference.checkAndSet(root, newRoot); + return 0; + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.add(comparator(), element, occurrences, result); + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + @Override + public int remove(@Nullable Object element, int occurrences) { + checkArgument(occurrences >= 0, "occurrences must be >= 0 but was %s", occurrences); + if (occurrences == 0) { + return count(element); + } + AvlNode root = rootReference.get(); + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot; + try { + @SuppressWarnings("unchecked") + E e = (E) element; + if (!range.contains(e) || root == null) { + return 0; + } + newRoot = root.remove(comparator(), e, occurrences, result); + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + @Override + public int setCount(@Nullable E element, int count) { + checkArgument(count >= 0); + if (!range.contains(element)) { + checkArgument(count == 0); + return 0; + } + + AvlNode root = rootReference.get(); + if (root == null) { + if (count > 0) { + add(element, count); + } + return 0; + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.setCount(comparator(), element, count, result); + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + @Override + public boolean setCount(@Nullable E element, int oldCount, int newCount) { + checkArgument(newCount >= 0); + checkArgument(oldCount >= 0); + checkArgument(range.contains(element)); + + AvlNode root = rootReference.get(); + if (root == null) { + if (oldCount == 0) { + if (newCount > 0) { + add(element, newCount); + } + return true; + } else { + return false; + } + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.setCount(comparator(), element, oldCount, newCount, result); + rootReference.checkAndSet(root, newRoot); + return result[0] == oldCount; + } + + private Entry wrapEntry(final AvlNode baseEntry) { + return new Multisets.AbstractEntry() { + @Override + public E getElement() { + return baseEntry.getElement(); + } + + @Override + public int getCount() { + int result = baseEntry.getCount(); + if (result == 0) { + return count(getElement()); + } else { + return result; + } + } + }; + } + + /** + * Returns the first node in the tree that is in range. + */ + @Nullable private AvlNode firstNode() { + AvlNode root = rootReference.get(); + if (root == null) { + return null; + } + AvlNode node; + if (range.hasLowerBound()) { + E endpoint = range.getLowerEndpoint(); + node = rootReference.get().ceiling(comparator(), endpoint); + if (node == null) { + return null; + } + if (range.getLowerBoundType() == BoundType.OPEN + && comparator().compare(endpoint, node.getElement()) == 0) { + node = node.succ; + } + } else { + node = header.succ; + } + return (node == header || !range.contains(node.getElement())) ? null : node; + } + + @Nullable private AvlNode lastNode() { + AvlNode root = rootReference.get(); + if (root == null) { + return null; + } + AvlNode node; + if (range.hasUpperBound()) { + E endpoint = range.getUpperEndpoint(); + node = rootReference.get().floor(comparator(), endpoint); + if (node == null) { + return null; + } + if (range.getUpperBoundType() == BoundType.OPEN + && comparator().compare(endpoint, node.getElement()) == 0) { + node = node.pred; + } + } else { + node = header.pred; + } + return (node == header || !range.contains(node.getElement())) ? null : node; + } + + @Override + Iterator> entryIterator() { + return new Iterator>() { + AvlNode current = firstNode(); + Entry prevEntry; + + @Override + public boolean hasNext() { + if (current == null) { + return false; + } else if (range.tooHigh(current.getElement())) { + current = null; + return false; + } else { + return true; + } + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Entry result = wrapEntry(current); + prevEntry = result; + if (current.succ == header) { + current = null; + } else { + current = current.succ; + } + return result; + } + + @Override + public void remove() { + checkState(prevEntry != null); + setCount(prevEntry.getElement(), 0); + prevEntry = null; + } + }; + } + + @Override + Iterator> descendingEntryIterator() { + return new Iterator>() { + AvlNode current = lastNode(); + Entry prevEntry = null; + + @Override + public boolean hasNext() { + if (current == null) { + return false; + } else if (range.tooLow(current.getElement())) { + current = null; + return false; + } else { + return true; + } + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Entry result = wrapEntry(current); + prevEntry = result; + if (current.pred == header) { + current = null; + } else { + current = current.pred; + } + return result; + } + + @Override + public void remove() { + checkState(prevEntry != null); + setCount(prevEntry.getElement(), 0); + prevEntry = null; + } + }; + } + + @Override + public SortedMultiset headMultiset(@Nullable E upperBound, BoundType boundType) { + return new TreeMultiset(rootReference, range.intersect(GeneralRange.upTo( + comparator(), + upperBound, + boundType)), header); + } + + @Override + public SortedMultiset tailMultiset(@Nullable E lowerBound, BoundType boundType) { + return new TreeMultiset(rootReference, range.intersect(GeneralRange.downTo( + comparator(), + lowerBound, + boundType)), header); + } + + static int distinctElements(@Nullable AvlNode node) { + return (node == null) ? 0 : node.distinctElements; + } + + private static final class Reference { + @Nullable private T value; + + @Nullable public T get() { + return value; + } + + public void checkAndSet(@Nullable T expected, T newValue) { + if (value != expected) { + throw new ConcurrentModificationException(); + } + value = newValue; + } + } + + private static final class AvlNode extends Multisets.AbstractEntry { + @Nullable private final E elem; + + // elemCount is 0 iff this node has been deleted. + private int elemCount; + + private int distinctElements; + private long totalCount; + private int height; + private AvlNode left; + private AvlNode right; + private AvlNode pred; + private AvlNode succ; + + AvlNode(@Nullable E elem, int elemCount) { + checkArgument(elemCount > 0); + this.elem = elem; + this.elemCount = elemCount; + this.totalCount = elemCount; + this.distinctElements = 1; + this.height = 1; + this.left = null; + this.right = null; + } + + public int count(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + return (left == null) ? 0 : left.count(comparator, e); + } else if (cmp > 0) { + return (right == null) ? 0 : right.count(comparator, e); + } else { + return elemCount; + } + } + + private AvlNode addRightChild(E e, int count) { + right = new AvlNode(e, count); + successor(this, right, succ); + height = Math.max(2, height); + distinctElements++; + totalCount += count; + return this; + } + + private AvlNode addLeftChild(E e, int count) { + left = new AvlNode(e, count); + successor(pred, left, this); + height = Math.max(2, height); + distinctElements++; + totalCount += count; + return this; + } + + AvlNode add(Comparator comparator, @Nullable E e, int count, int[] result) { + /* + * It speeds things up considerably to unconditionally add count to totalCount here, + * but that destroys failure atomicity in the case of count overflow. =( + */ + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return addLeftChild(e, count); + } + int initHeight = initLeft.height; + + left = initLeft.add(comparator, e, count, result); + if (result[0] == 0) { + distinctElements++; + } + this.totalCount += count; + return (left.height == initHeight) ? this : rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return addRightChild(e, count); + } + int initHeight = initRight.height; + + right = initRight.add(comparator, e, count, result); + if (result[0] == 0) { + distinctElements++; + } + this.totalCount += count; + return (right.height == initHeight) ? this : rebalance(); + } + + // adding count to me! No rebalance possible. + result[0] = elemCount; + long resultCount = (long) elemCount + count; + checkArgument(resultCount <= Integer.MAX_VALUE); + this.elemCount += count; + this.totalCount += count; + return this; + } + + AvlNode remove(Comparator comparator, @Nullable E e, int count, int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return this; + } + + left = initLeft.remove(comparator, e, count, result); + + if (result[0] > 0) { + if (count >= result[0]) { + this.distinctElements--; + this.totalCount -= result[0]; + } else { + this.totalCount -= count; + } + } + return (result[0] == 0) ? this : rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return this; + } + + right = initRight.remove(comparator, e, count, result); + + if (result[0] > 0) { + if (count >= result[0]) { + this.distinctElements--; + this.totalCount -= result[0]; + } else { + this.totalCount -= count; + } + } + return rebalance(); + } + + // removing count from me! + result[0] = elemCount; + if (count >= elemCount) { + return deleteMe(); + } else { + this.elemCount -= count; + this.totalCount -= count; + return this; + } + } + + AvlNode setCount(Comparator comparator, @Nullable E e, int count, int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return (count > 0) ? addLeftChild(e, count) : this; + } + + left = initLeft.setCount(comparator, e, count, result); + + if (count == 0 && result[0] != 0) { + this.distinctElements--; + } else if (count > 0 && result[0] == 0) { + this.distinctElements++; + } + + this.totalCount += count - result[0]; + return rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return (count > 0) ? addRightChild(e, count) : this; + } + + right = initRight.setCount(comparator, e, count, result); + + if (count == 0 && result[0] != 0) { + this.distinctElements--; + } else if (count > 0 && result[0] == 0) { + this.distinctElements++; + } + + this.totalCount += count - result[0]; + return rebalance(); + } + + // setting my count + result[0] = elemCount; + if (count == 0) { + return deleteMe(); + } + this.totalCount += count - elemCount; + this.elemCount = count; + return this; + } + + AvlNode setCount( + Comparator comparator, + @Nullable E e, + int expectedCount, + int newCount, + int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + if (expectedCount == 0 && newCount > 0) { + return addLeftChild(e, newCount); + } + return this; + } + + left = initLeft.setCount(comparator, e, expectedCount, newCount, result); + + if (result[0] == expectedCount) { + if (newCount == 0 && result[0] != 0) { + this.distinctElements--; + } else if (newCount > 0 && result[0] == 0) { + this.distinctElements++; + } + this.totalCount += newCount - result[0]; + } + return rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + if (expectedCount == 0 && newCount > 0) { + return addRightChild(e, newCount); + } + return this; + } + + right = initRight.setCount(comparator, e, expectedCount, newCount, result); + + if (result[0] == expectedCount) { + if (newCount == 0 && result[0] != 0) { + this.distinctElements--; + } else if (newCount > 0 && result[0] == 0) { + this.distinctElements++; + } + this.totalCount += newCount - result[0]; + } + return rebalance(); + } + + // setting my count + result[0] = elemCount; + if (expectedCount == elemCount) { + if (newCount == 0) { + return deleteMe(); + } + this.totalCount += newCount - elemCount; + this.elemCount = newCount; + } + return this; + } + + private AvlNode deleteMe() { + int oldElemCount = this.elemCount; + this.elemCount = 0; + successor(pred, succ); + if (left == null) { + return right; + } else if (right == null) { + return left; + } else if (left.height >= right.height) { + AvlNode newTop = pred; + // newTop is the maximum node in my left subtree + newTop.left = left.removeMax(newTop); + newTop.right = right; + newTop.distinctElements = distinctElements - 1; + newTop.totalCount = totalCount - oldElemCount; + return newTop.rebalance(); + } else { + AvlNode newTop = succ; + newTop.right = right.removeMin(newTop); + newTop.left = left; + newTop.distinctElements = distinctElements - 1; + newTop.totalCount = totalCount - oldElemCount; + return newTop.rebalance(); + } + } + + // Removes the minimum node from this subtree to be reused elsewhere + private AvlNode removeMin(AvlNode node) { + if (left == null) { + return right; + } else { + left = left.removeMin(node); + distinctElements--; + totalCount -= node.elemCount; + return rebalance(); + } + } + + // Removes the maximum node from this subtree to be reused elsewhere + private AvlNode removeMax(AvlNode node) { + if (right == null) { + return left; + } else { + right = right.removeMax(node); + distinctElements--; + totalCount -= node.elemCount; + return rebalance(); + } + } + + private void recomputeMultiset() { + this.distinctElements = 1 + TreeMultiset.distinctElements(left) + + TreeMultiset.distinctElements(right); + this.totalCount = elemCount + totalCount(left) + totalCount(right); + } + + private void recomputeHeight() { + this.height = 1 + Math.max(height(left), height(right)); + } + + private void recompute() { + recomputeMultiset(); + recomputeHeight(); + } + + private AvlNode rebalance() { + switch (balanceFactor()) { + case -2: + if (right.balanceFactor() > 0) { + right = right.rotateRight(); + } + return rotateLeft(); + case 2: + if (left.balanceFactor() < 0) { + left = left.rotateLeft(); + } + return rotateRight(); + default: + recomputeHeight(); + return this; + } + } + + private int balanceFactor() { + return height(left) - height(right); + } + + private AvlNode rotateLeft() { + checkState(right != null); + AvlNode newTop = right; + this.right = newTop.left; + newTop.left = this; + newTop.totalCount = this.totalCount; + newTop.distinctElements = this.distinctElements; + this.recompute(); + newTop.recomputeHeight(); + return newTop; + } + + private AvlNode rotateRight() { + checkState(left != null); + AvlNode newTop = left; + this.left = newTop.right; + newTop.right = this; + newTop.totalCount = this.totalCount; + newTop.distinctElements = this.distinctElements; + this.recompute(); + newTop.recomputeHeight(); + return newTop; + } + + private static long totalCount(@Nullable AvlNode node) { + return (node == null) ? 0 : node.totalCount; + } + + private static int height(@Nullable AvlNode node) { + return (node == null) ? 0 : node.height; + } + + @Nullable private AvlNode ceiling(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + return (left == null) ? this : Objects.firstNonNull(left.ceiling(comparator, e), this); + } else if (cmp == 0) { + return this; + } else { + return (right == null) ? null : right.ceiling(comparator, e); + } + } + + @Nullable private AvlNode floor(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp > 0) { + return (right == null) ? this : Objects.firstNonNull(right.floor(comparator, e), this); + } else if (cmp == 0) { + return this; + } else { + return (left == null) ? null : left.floor(comparator, e); + } + } + + @Override + public E getElement() { + return elem; + } + + @Override + public int getCount() { + return elemCount; + } + + @Override + public String toString() { + return Multisets.immutableEntry(getElement(), getCount()).toString(); + } + } + + private static void successor(AvlNode a, AvlNode b) { + a.succ = b; + b.pred = a; + } + + private static void successor(AvlNode a, AvlNode b, AvlNode c) { + successor(a, b); + successor(b, c); + } + + /* + * TODO(jlevy): Decide whether entrySet() should return entries with an equals() method that + * calls the comparator to compare the two keys. If that change is made, + * AbstractMultiset.equals() can simply check whether two multisets have equal entry sets. + */ + + /** + * @serialData the comparator, the number of distinct elements, the first element, its count, the + * second element, its count, and so on + */ + @GwtIncompatible("java.io.ObjectOutputStream") + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(elementSet().comparator()); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible("java.io.ObjectInputStream") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") + // reading data stored by writeObject + Comparator comparator = (Comparator) stream.readObject(); + Serialization.getFieldSetter(AbstractSortedMultiset.class, "comparator").set(this, comparator); + Serialization.getFieldSetter(TreeMultiset.class, "range").set( + this, + GeneralRange.all(comparator)); + Serialization.getFieldSetter(TreeMultiset.class, "rootReference").set( + this, + new Reference>()); + AvlNode header = new AvlNode(null, 1); + Serialization.getFieldSetter(TreeMultiset.class, "header").set(this, header); + successor(header, header); + Serialization.populateMultiset(this, stream); + } + + @GwtIncompatible("not needed in emulated source") private static final long serialVersionUID = 1; +} diff --git a/guava/src/com/google/common/collect/TreeRangeSet.java b/guava/src/com/google/common/collect/TreeRangeSet.java new file mode 100644 index 0000000..6c0fa5c --- /dev/null +++ b/guava/src/com/google/common/collect/TreeRangeSet.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + +import java.util.Collection; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * An implementation of {@link RangeSet} backed by a {@link TreeMap}. + * + * @author Louis Wasserman + */ +@GwtIncompatible("uses NavigableMap") final class TreeRangeSet + extends RangeSet { + // TODO(user): override inefficient defaults + + private final NavigableMap, Range> rangesByLowerCut; + + /** + * Creates an empty {@code TreeRangeSet} instance. + */ + public static TreeRangeSet create() { + return new TreeRangeSet(new TreeMap, Range>()); + } + + private TreeRangeSet(NavigableMap, Range> rangesByLowerCut) { + this.rangesByLowerCut = rangesByLowerCut; + } + + private transient Set> asRanges; + + @Override + public Set> asRanges() { + Set> result = asRanges; + return (result == null) ? asRanges = new AsRanges() : result; + } + + final class AsRanges extends ForwardingCollection> implements Set> { + @Override + protected Collection> delegate() { + return rangesByLowerCut.values(); + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override + public boolean equals(@Nullable Object o) { + return Sets.equalsImpl(this, o); + } + } + + @Override + @Nullable + public Range rangeContaining(C value) { + checkNotNull(value); + Entry, Range> floorEntry = rangesByLowerCut.floorEntry(Cut.belowValue(value)); + if (floorEntry != null && floorEntry.getValue().contains(value)) { + return floorEntry.getValue(); + } else { + // TODO(kevinb): revisit this design choice + return null; + } + } + + @Override + public boolean encloses(Range range) { + checkNotNull(range); + Entry, Range> floorEntry = rangesByLowerCut.floorEntry(range.lowerBound); + return floorEntry != null && floorEntry.getValue().encloses(range); + } + + @Override + public void add(Range rangeToAdd) { + checkNotNull(rangeToAdd); + + if (rangeToAdd.isEmpty()) { + return; + } + + // We will use { } to illustrate ranges currently in the range set, and < > + // to illustrate rangeToAdd. + Cut lbToAdd = rangeToAdd.lowerBound; + Cut ubToAdd = rangeToAdd.upperBound; + + Entry, Range> entryBelowLB = rangesByLowerCut.lowerEntry(lbToAdd); + if (entryBelowLB != null) { + // { < + Range rangeBelowLB = entryBelowLB.getValue(); + if (rangeBelowLB.upperBound.compareTo(lbToAdd) >= 0) { + // { < }, and we will need to coalesce + if (rangeBelowLB.upperBound.compareTo(ubToAdd) >= 0) { + // { < > } + ubToAdd = rangeBelowLB.upperBound; + /* + * TODO(cpovirk): can we just "return;" here? Or, can we remove this if() entirely? If + * not, add tests to demonstrate the problem with each approach + */ + } + lbToAdd = rangeBelowLB.lowerBound; + } + } + + Entry, Range> entryBelowUB = rangesByLowerCut.floorEntry(ubToAdd); + if (entryBelowUB != null) { + // { > + Range rangeBelowUB = entryBelowUB.getValue(); + if (rangeBelowUB.upperBound.compareTo(ubToAdd) >= 0) { + // { > }, and we need to coalesce + ubToAdd = rangeBelowUB.upperBound; + } + } + + // Remove ranges which are strictly enclosed. + rangesByLowerCut.subMap(lbToAdd, ubToAdd).clear(); + + replaceRangeWithSameLowerBound(new Range(lbToAdd, ubToAdd)); + } + + @Override + public void remove(Range rangeToRemove) { + checkNotNull(rangeToRemove); + + if (rangeToRemove.isEmpty()) { + return; + } + + // We will use { } to illustrate ranges currently in the range set, and < > + // to illustrate rangeToRemove. + + Entry, Range> entryBelowLB = rangesByLowerCut.lowerEntry(rangeToRemove.lowerBound); + if (entryBelowLB != null) { + // { < + Range rangeBelowLB = entryBelowLB.getValue(); + if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) { + // { < }, and we will need to subdivide + if (rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + // { < > } + replaceRangeWithSameLowerBound( + new Range(rangeToRemove.upperBound, rangeBelowLB.upperBound)); + } + replaceRangeWithSameLowerBound( + new Range(rangeBelowLB.lowerBound, rangeToRemove.lowerBound)); + } + } + + Entry, Range> entryBelowUB = rangesByLowerCut.floorEntry(rangeToRemove.upperBound); + if (entryBelowUB != null) { + // { > + Range rangeBelowUB = entryBelowUB.getValue(); + if (rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + // { > } + replaceRangeWithSameLowerBound( + new Range(rangeToRemove.upperBound, rangeBelowUB.upperBound)); + } + } + + rangesByLowerCut.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); + } + + private void replaceRangeWithSameLowerBound(Range range) { + if (range.isEmpty()) { + rangesByLowerCut.remove(range.lowerBound); + } else { + rangesByLowerCut.put(range.lowerBound, range); + } + } + + private transient RangeSet complement; + + @Override + public RangeSet complement() { + RangeSet result = complement; + return (result == null) ? complement = createComplement() : result; + } + + private RangeSet createComplement() { + return new StandardComplement(this); + } +} diff --git a/guava/src/com/google/common/collect/UnmodifiableIterator.java b/guava/src/com/google/common/collect/UnmodifiableIterator.java new file mode 100644 index 0000000..5cff61b --- /dev/null +++ b/guava/src/com/google/common/collect/UnmodifiableIterator.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator that does not support {@link #remove}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public abstract class UnmodifiableIterator implements Iterator { + /** Constructor for use by subclasses. */ + protected UnmodifiableIterator() {} + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/collect/UnmodifiableListIterator.java b/guava/src/com/google/common/collect/UnmodifiableListIterator.java new file mode 100644 index 0000000..fa71bdc --- /dev/null +++ b/guava/src/com/google/common/collect/UnmodifiableListIterator.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.ListIterator; + +/** + * A list iterator that does not support {@link #remove}, {@link #add}, or + * {@link #set}. + * + * @since 7.0 + * @author Louis Wasserman + */ +@GwtCompatible +public abstract class UnmodifiableListIterator + extends UnmodifiableIterator implements ListIterator { + /** Constructor for use by subclasses. */ + protected UnmodifiableListIterator() {} + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final void add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public final void set(E e) { + throw new UnsupportedOperationException(); + } +} diff --git a/guava/src/com/google/common/collect/UsingToStringOrdering.java b/guava/src/com/google/common/collect/UsingToStringOrdering.java new file mode 100644 index 0000000..d1c9feb --- /dev/null +++ b/guava/src/com/google/common/collect/UsingToStringOrdering.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; + +/** An ordering that uses the reverse of the natural order of the values. */ +@GwtCompatible(serializable = true) +final class UsingToStringOrdering + extends Ordering implements Serializable { + static final UsingToStringOrdering INSTANCE = new UsingToStringOrdering(); + + @Override public int compare(Object left, Object right) { + return left.toString().compareTo(right.toString()); + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.usingToString()"; + } + + private UsingToStringOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/collect/WellBehavedMap.java b/guava/src/com/google/common/collect/WellBehavedMap.java new file mode 100644 index 0000000..c68cc5e --- /dev/null +++ b/guava/src/com/google/common/collect/WellBehavedMap.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Workaround for + * + * EnumMap bug. If you want to pass an {@code EnumMap}, with the + * intention of using its {@code entrySet()} method, you should + * wrap the {@code EnumMap} in this class instead. + * + *

This class is not thread-safe even if the underlying map is. + * + * @author Dimitris Andreou + */ +@GwtCompatible +final class WellBehavedMap extends ForwardingMap { + private final Map delegate; + private Set> entrySet; + + private WellBehavedMap(Map delegate) { + this.delegate = delegate; + } + + /** + * Wraps the given map into a {@code WellBehavedEntriesMap}, which + * intercepts its {@code entrySet()} method by taking the + * {@code Set keySet()} and transforming it to + * {@code Set>}. All other invocations are delegated as-is. + */ + static WellBehavedMap wrap(Map delegate) { + return new WellBehavedMap(delegate); + } + + @Override protected Map delegate() { + return delegate; + } + + @Override public Set> entrySet() { + Set> es = entrySet; + if (es != null) { + return es; + } + return entrySet = new EntrySet(); + } + + private final class EntrySet extends Maps.EntrySet { + @Override + Map map() { + return WellBehavedMap.this; + } + + @Override + public Iterator> iterator() { + return new TransformedIterator>(keySet().iterator()) { + @Override + Entry transform(final K key) { + return new AbstractMapEntry() { + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return get(key); + } + + @Override + public V setValue(V value) { + return put(key, value); + } + }; + } + }; + } + } +} diff --git a/guava/src/com/google/common/collect/package-info.java b/guava/src/com/google/common/collect/package-info.java new file mode 100644 index 0000000..054b944 --- /dev/null +++ b/guava/src/com/google/common/collect/package-info.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains generic collection interfaces and implementations, and + * other utilities for working with collections. It is a part of the open-source + * Guava libraries. + * + *

Collection Types

+ * + *
+ *
{@link com.google.common.collect.BiMap} + *
An extension of {@link java.util.Map} that guarantees the uniqueness of + * its values as well as that of its keys. This is sometimes called an + * "invertible map," since the restriction on values enables it to support + * an {@linkplain com.google.common.collect.BiMap#inverse inverse view} -- + * which is another instance of {@code BiMap}. + * + *
{@link com.google.common.collect.Multiset} + *
An extension of {@link java.util.Collection} that may contain duplicate + * values like a {@link java.util.List}, yet has order-independent equality + * like a {@link java.util.Set}. One typical use for a multiset is to + * represent a histogram. + * + *
{@link com.google.common.collect.Multimap} + *
A new type, which is similar to {@link java.util.Map}, but may contain + * multiple entries with the same key. Some behaviors of + * {@link com.google.common.collect.Multimap} are left unspecified and are + * provided only by the subtypes mentioned below. + * + *
{@link com.google.common.collect.ListMultimap} + *
An extension of {@link com.google.common.collect.Multimap} which permits + * duplicate entries, supports random access of values for a particular key, + * and has partially order-dependent equality as defined by + * {@link com.google.common.collect.ListMultimap#equals(Object)}. {@code + * ListMultimap} takes its name from the fact that the {@linkplain + * com.google.common.collect.ListMultimap#get collection of values} + * associated with a given key fulfills the {@link java.util.List} contract. + * + *
{@link com.google.common.collect.SetMultimap} + *
An extension of {@link com.google.common.collect.Multimap} which has + * order-independent equality and does not allow duplicate entries; that is, + * while a key may appear twice in a {@code SetMultimap}, each must map to a + * different value. {@code SetMultimap} takes its name from the fact that + * the {@linkplain com.google.common.collect.SetMultimap#get collection of + * values} associated with a given key fulfills the {@link java.util.Set} + * contract. + * + *
{@link com.google.common.collect.SortedSetMultimap} + *
An extension of {@link com.google.common.collect.SetMultimap} for which + * the {@linkplain com.google.common.collect.SortedSetMultimap#get + * collection values} associated with a given key is a + * {@link java.util.SortedSet}. + * + *
{@link com.google.common.collect.Table} + *
A new type, which is similar to {@link java.util.Map}, but which indexes + * its values by an ordered pair of keys, a row key and column key. + * + *
{@link com.google.common.collect.ClassToInstanceMap} + *
An extension of {@link java.util.Map} that associates a raw type with an + * instance of that type. + *
+ * + *

Collection Implementations

+ * + *

of {@link java.util.List}

+ *
    + *
  • {@link com.google.common.collect.ImmutableList} + *
+ * + *

of {@link java.util.Set}

+ *
    + *
  • {@link com.google.common.collect.ImmutableSet} + *
  • {@link com.google.common.collect.ImmutableSortedSet} + *
  • {@link com.google.common.collect.ContiguousSet} (see {@code Ranges}) + *
+ * + *

of {@link java.util.Map}

+ *
    + *
  • {@link com.google.common.collect.ImmutableMap} + *
  • {@link com.google.common.collect.ImmutableSortedMap} + *
  • {@link com.google.common.collect.MapMaker} + *
+ * + *

of {@link com.google.common.collect.BiMap}

+ *
    + *
  • {@link com.google.common.collect.ImmutableBiMap} + *
  • {@link com.google.common.collect.HashBiMap} + *
  • {@link com.google.common.collect.EnumBiMap} + *
  • {@link com.google.common.collect.EnumHashBiMap} + *
+ * + *

of {@link com.google.common.collect.Multiset}

+ *
    + *
  • {@link com.google.common.collect.ImmutableMultiset} + *
  • {@link com.google.common.collect.HashMultiset} + *
  • {@link com.google.common.collect.LinkedHashMultiset} + *
  • {@link com.google.common.collect.TreeMultiset} + *
  • {@link com.google.common.collect.EnumMultiset} + *
  • {@link com.google.common.collect.ConcurrentHashMultiset} + *
+ * + *

of {@link com.google.common.collect.Multimap}

+ *
    + *
  • {@link com.google.common.collect.ImmutableMultimap} + *
  • {@link com.google.common.collect.ImmutableListMultimap} + *
  • {@link com.google.common.collect.ImmutableSetMultimap} + *
  • {@link com.google.common.collect.ArrayListMultimap} + *
  • {@link com.google.common.collect.HashMultimap} + *
  • {@link com.google.common.collect.TreeMultimap} + *
  • {@link com.google.common.collect.LinkedHashMultimap} + *
  • {@link com.google.common.collect.LinkedListMultimap} + *
+ * + *

of {@link com.google.common.collect.Table}

+ *
    + *
  • {@link com.google.common.collect.ImmutableTable} + *
  • {@link com.google.common.collect.ArrayTable} + *
  • {@link com.google.common.collect.HashBasedTable} + *
  • {@link com.google.common.collect.TreeBasedTable} + *
+ * + *

of {@link com.google.common.collect.ClassToInstanceMap}

+ *
    + *
  • {@link com.google.common.collect.ImmutableClassToInstanceMap} + *
  • {@link com.google.common.collect.MutableClassToInstanceMap} + *
+ * + *

Classes of static utility methods

+ * + *
    + *
  • {@link com.google.common.collect.Collections2} + *
  • {@link com.google.common.collect.Iterators} + *
  • {@link com.google.common.collect.Iterables} + *
  • {@link com.google.common.collect.Lists} + *
  • {@link com.google.common.collect.Maps} + *
  • {@link com.google.common.collect.Queues} + *
  • {@link com.google.common.collect.Sets} + *
  • {@link com.google.common.collect.Multisets} + *
  • {@link com.google.common.collect.Multimaps} + *
  • {@link com.google.common.collect.Tables} + *
  • {@link com.google.common.collect.ObjectArrays} + *
+ * + *

Comparison

+ * + *
    + *
  • {@link com.google.common.collect.Ordering} + *
  • {@link com.google.common.collect.ComparisonChain} + *
+ * + *

Abstract implementations

+ * + *
    + *
  • {@link com.google.common.collect.AbstractIterator} + *
  • {@link com.google.common.collect.AbstractSequentialIterator} + *
  • {@link com.google.common.collect.ImmutableCollection} + *
  • {@link com.google.common.collect.UnmodifiableIterator} + *
  • {@link com.google.common.collect.UnmodifiableListIterator} + *
+ * + *

Ranges

+ * + *
    + *
  • {@link com.google.common.collect.Range} + *
  • {@link com.google.common.collect.Ranges} + *
  • {@link com.google.common.collect.DiscreteDomain} + *
  • {@link com.google.common.collect.DiscreteDomains} + *
  • {@link com.google.common.collect.ContiguousSet} + *
+ * + *

Other

+ * + *
    + *
  • {@link com.google.common.collect.Interner}, + * {@link com.google.common.collect.Interners} + *
  • {@link com.google.common.collect.Constraint}, + * {@link com.google.common.collect.Constraints} + *
  • {@link com.google.common.collect.MapConstraint}, + * {@link com.google.common.collect.MapConstraints} + *
  • {@link com.google.common.collect.MapDifference}, + * {@link com.google.common.collect.SortedMapDifference} + *
  • {@link com.google.common.collect.MinMaxPriorityQueue} + *
  • {@link com.google.common.collect.PeekingIterator} + *
+ * + *

Forwarding collections

+ * + *
    + *
  • {@link com.google.common.collect.ForwardingCollection} + *
  • {@link com.google.common.collect.ForwardingConcurrentMap} + *
  • {@link com.google.common.collect.ForwardingIterator} + *
  • {@link com.google.common.collect.ForwardingList} + *
  • {@link com.google.common.collect.ForwardingListIterator} + *
  • {@link com.google.common.collect.ForwardingListMultimap} + *
  • {@link com.google.common.collect.ForwardingMap} + *
  • {@link com.google.common.collect.ForwardingMapEntry} + *
  • {@link com.google.common.collect.ForwardingMultimap} + *
  • {@link com.google.common.collect.ForwardingMultiset} + *
  • {@link com.google.common.collect.ForwardingNavigableMap} + *
  • {@link com.google.common.collect.ForwardingNavigableSet} + *
  • {@link com.google.common.collect.ForwardingObject} + *
  • {@link com.google.common.collect.ForwardingQueue} + *
  • {@link com.google.common.collect.ForwardingSet} + *
  • {@link com.google.common.collect.ForwardingSetMultimap} + *
  • {@link com.google.common.collect.ForwardingSortedMap} + *
  • {@link com.google.common.collect.ForwardingSortedSet} + *
  • {@link com.google.common.collect.ForwardingSortedSetMultimap} + *
  • {@link com.google.common.collect.ForwardingTable} + *
+ */ +@javax.annotation.ParametersAreNonnullByDefault +package com.google.common.collect; diff --git a/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java b/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java new file mode 100644 index 0000000..f614598 --- /dev/null +++ b/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an event handling method as being thread-safe. This annotation + * indicates that EventBus may invoke the event handler simultaneously from + * multiple threads. + * + *

This does not mark the method as an event handler, and so should be used + * in combination with {@link Subscribe}. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Beta +public @interface AllowConcurrentEvents { +} diff --git a/guava/src/com/google/common/eventbus/AnnotatedHandlerFinder.java b/guava/src/com/google/common/eventbus/AnnotatedHandlerFinder.java new file mode 100644 index 0000000..1ab63ac --- /dev/null +++ b/guava/src/com/google/common/eventbus/AnnotatedHandlerFinder.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.reflect.TypeToken; + +import java.lang.reflect.Method; +import java.util.Set; + +/** + * A {@link HandlerFindingStrategy} for collecting all event handler methods that are marked with + * the {@link Subscribe} annotation. + * + * @author Cliff Biffle + * @author Louis Wasserman + */ +class AnnotatedHandlerFinder implements HandlerFindingStrategy { + /** + * {@inheritDoc} + * + * This implementation finds all methods marked with a {@link Subscribe} annotation. + */ + @Override + public Multimap, EventHandler> findAllHandlers(Object listener) { + Multimap, EventHandler> methodsInListener = HashMultimap.create(); + Class clazz = listener.getClass(); + Set> supers = TypeToken.of(clazz).getTypes().rawTypes(); + + for (Method method : clazz.getMethods()) { + /* + * Iterate over each distinct method of {@code clazz}, checking if it is annotated with + * @Subscribe by any of the superclasses or superinterfaces that declare it. + */ + for (Class c : supers) { + try { + Method m = c.getMethod(method.getName(), method.getParameterTypes()); + if (m.isAnnotationPresent(Subscribe.class)) { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1) { + throw new IllegalArgumentException("Method " + method + + " has @Subscribe annotation, but requires " + parameterTypes.length + + " arguments. Event handler methods must require a single argument."); + } + Class eventType = parameterTypes[0]; + EventHandler handler = makeHandler(listener, method); + + methodsInListener.put(eventType, handler); + break; + } + } catch (NoSuchMethodException ignored) { + // Move on. + } + } + } + return methodsInListener; + } + + /** + * Creates an {@code EventHandler} for subsequently calling {@code method} on + * {@code listener}. + * Selects an EventHandler implementation based on the annotations on + * {@code method}. + * + * @param listener object bearing the event handler method. + * @param method the event handler method to wrap in an EventHandler. + * @return an EventHandler that will call {@code method} on {@code listener} + * when invoked. + */ + private static EventHandler makeHandler(Object listener, Method method) { + EventHandler wrapper; + if (methodIsDeclaredThreadSafe(method)) { + wrapper = new EventHandler(listener, method); + } else { + wrapper = new SynchronizedEventHandler(listener, method); + } + return wrapper; + } + + /** + * Checks whether {@code method} is thread-safe, as indicated by the + * {@link AllowConcurrentEvents} annotation. + * + * @param method handler method to check. + * @return {@code true} if {@code handler} is marked as thread-safe, + * {@code false} otherwise. + */ + private static boolean methodIsDeclaredThreadSafe(Method method) { + return method.getAnnotation(AllowConcurrentEvents.class) != null; + } +} diff --git a/guava/src/com/google/common/eventbus/AsyncEventBus.java b/guava/src/com/google/common/eventbus/AsyncEventBus.java new file mode 100644 index 0000000..81ed4fe --- /dev/null +++ b/guava/src/com/google/common/eventbus/AsyncEventBus.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; + +/** + * An {@link EventBus} that takes the Executor of your choice and uses it to + * dispatch events, allowing dispatch to occur asynchronously. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class AsyncEventBus extends EventBus { + private final Executor executor; + + /** the queue of events is shared across all threads */ + private final ConcurrentLinkedQueue eventsToDispatch = + new ConcurrentLinkedQueue(); + + /** + * Creates a new AsyncEventBus that will use {@code executor} to dispatch + * events. Assigns {@code identifier} as the bus's name for logging purposes. + * + * @param identifier short name for the bus, for logging purposes. + * @param executor Executor to use to dispatch events. It is the caller's + * responsibility to shut down the executor after the last event has + * been posted to this event bus. + */ + public AsyncEventBus(String identifier, Executor executor) { + super(identifier); + this.executor = executor; + } + + /** + * Creates a new AsyncEventBus that will use {@code executor} to dispatch + * events. + * + * @param executor Executor to use to dispatch events. It is the caller's + * responsibility to shut down the executor after the last event has + * been posted to this event bus. + */ + public AsyncEventBus(Executor executor) { + this.executor = executor; + } + + @Override + void enqueueEvent(Object event, EventHandler handler) { + eventsToDispatch.offer(new EventWithHandler(event, handler)); + } + + /** + * Dispatch {@code events} in the order they were posted, regardless of + * the posting thread. + */ + @SuppressWarnings("deprecation") // only deprecated for external subclasses + @Override + protected void dispatchQueuedEvents() { + while (true) { + EventWithHandler eventWithHandler = eventsToDispatch.poll(); + if (eventWithHandler == null) { + break; + } + + dispatch(eventWithHandler.event, eventWithHandler.handler); + } + } + + /** + * Calls the {@link #executor} to dispatch {@code event} to {@code handler}. + */ + @Override + void dispatch(final Object event, final EventHandler handler) { + executor.execute(new Runnable() { + @Override + @SuppressWarnings("synthetic-access") + public void run() { + AsyncEventBus.super.dispatch(event, handler); + } + }); + } + +} diff --git a/guava/src/com/google/common/eventbus/DeadEvent.java b/guava/src/com/google/common/eventbus/DeadEvent.java new file mode 100644 index 0000000..8265d5a --- /dev/null +++ b/guava/src/com/google/common/eventbus/DeadEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; + +/** + * Wraps an event that was posted, but which had no subscribers and thus could + * not be delivered. + * + *

Subscribing a DeadEvent handler is useful for debugging or logging, as it + * can detect misconfigurations in a system's event distribution. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class DeadEvent { + + private final Object source; + private final Object event; + + /** + * Creates a new DeadEvent. + * + * @param source object broadcasting the DeadEvent (generally the + * {@link EventBus}). + * @param event the event that could not be delivered. + */ + public DeadEvent(Object source, Object event) { + this.source = source; + this.event = event; + } + + /** + * Returns the object that originated this event (not the object that + * originated the wrapped event). This is generally an {@link EventBus}. + * + * @return the source of this event. + */ + public Object getSource() { + return source; + } + + /** + * Returns the wrapped, 'dead' event, which the system was unable to deliver + * to any registered handler. + * + * @return the 'dead' event that could not be delivered. + */ + public Object getEvent() { + return event; + } + +} diff --git a/guava/src/com/google/common/eventbus/EventBus.java b/guava/src/com/google/common/eventbus/EventBus.java new file mode 100644 index 0000000..37d6044 --- /dev/null +++ b/guava/src/com/google/common/eventbus/EventBus.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; +import com.google.common.reflect.TypeToken; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Dispatches events to listeners, and provides ways for listeners to register + * themselves. + * + *

The EventBus allows publish-subscribe-style communication between + * components without requiring the components to explicitly register with one + * another (and thus be aware of each other). It is designed exclusively to + * replace traditional Java in-process event distribution using explicit + * registration. It is not a general-purpose publish-subscribe system, + * nor is it intended for interprocess communication. + * + *

Receiving Events

+ * To receive events, an object should:
    + *
  1. Expose a public method, known as the event handler, which accepts + * a single argument of the type of event desired;
  2. + *
  3. Mark it with a {@link Subscribe} annotation;
  4. + *
  5. Pass itself to an EventBus instance's {@link #register(Object)} method. + *
  6. + *
+ * + *

Posting Events

+ * To post an event, simply provide the event object to the + * {@link #post(Object)} method. The EventBus instance will determine the type + * of event and route it to all registered listeners. + * + *

Events are routed based on their type — an event will be delivered + * to any handler for any type to which the event is assignable. This + * includes implemented interfaces, all superclasses, and all interfaces + * implemented by superclasses. + * + *

When {@code post} is called, all registered handlers for an event are run + * in sequence, so handlers should be reasonably quick. If an event may trigger + * an extended process (such as a database load), spawn a thread or queue it for + * later. (For a convenient way to do this, use an {@link AsyncEventBus}.) + * + *

Handler Methods

+ * Event handler methods must accept only one argument: the event. + * + *

Handlers should not, in general, throw. If they do, the EventBus will + * catch and log the exception. This is rarely the right solution for error + * handling and should not be relied upon; it is intended solely to help find + * problems during development. + * + *

The EventBus guarantees that it will not call a handler method from + * multiple threads simultaneously, unless the method explicitly allows it by + * bearing the {@link AllowConcurrentEvents} annotation. If this annotation is + * not present, handler methods need not worry about being reentrant, unless + * also called from outside the EventBus. + * + *

Dead Events

+ * If an event is posted, but no registered handlers can accept it, it is + * considered "dead." To give the system a second chance to handle dead events, + * they are wrapped in an instance of {@link DeadEvent} and reposted. + * + *

If a handler for a supertype of all events (such as Object) is registered, + * no event will ever be considered dead, and no DeadEvents will be generated. + * Accordingly, while DeadEvent extends {@link Object}, a handler registered to + * receive any Object will never receive a DeadEvent. + * + *

This class is safe for concurrent use. + * + *

See the Guava User Guide article on + * {@code EventBus}. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class EventBus { + + /** + * All registered event handlers, indexed by event type. + */ + private final SetMultimap, EventHandler> handlersByType = + Multimaps.newSetMultimap(new ConcurrentHashMap, Collection>(), + new Supplier>() { + @Override + public Set get() { + return newHandlerSet(); + } + }); + + /** + * Logger for event dispatch failures. Named by the fully-qualified name of + * this class, followed by the identifier provided at construction. + */ + private final Logger logger; + + /** + * Strategy for finding handler methods in registered objects. Currently, + * only the {@link AnnotatedHandlerFinder} is supported, but this is + * encapsulated for future expansion. + */ + private final HandlerFindingStrategy finder = new AnnotatedHandlerFinder(); + + /** queues of events for the current thread to dispatch */ + private final ThreadLocal> + eventsToDispatch = + new ThreadLocal>() { + @Override protected ConcurrentLinkedQueue initialValue() { + return new ConcurrentLinkedQueue(); + } + }; + + /** true if the current thread is currently dispatching an event */ + private final ThreadLocal isDispatching = + new ThreadLocal() { + @Override protected Boolean initialValue() { + return false; + } + }; + + /** + * A thread-safe cache for flattenHierarchy(). The Class class is immutable. + */ + private final LoadingCache, Set>> flattenHierarchyCache = + CacheBuilder.newBuilder() + .weakKeys() + .build(new CacheLoader, Set>>() { + @SuppressWarnings({"unchecked", "rawtypes"}) // safe cast + @Override + public Set> load(Class concreteClass) throws Exception { + return (Set) TypeToken.of(concreteClass).getTypes().rawTypes(); + } + }); + + /** + * Creates a new EventBus named "default". + */ + public EventBus() { + this("default"); + } + + /** + * Creates a new EventBus with the given {@code identifier}. + * + * @param identifier a brief name for this bus, for logging purposes. Should + * be a valid Java identifier. + */ + public EventBus(String identifier) { + logger = Logger.getLogger(EventBus.class.getName() + "." + identifier); + } + + /** + * Registers all handler methods on {@code object} to receive events. + * Handler methods are selected and classified using this EventBus's + * {@link HandlerFindingStrategy}; the default strategy is the + * {@link AnnotatedHandlerFinder}. + * + * @param object object whose handler methods should be registered. + */ + public void register(Object object) { + handlersByType.putAll(finder.findAllHandlers(object)); + } + + /** + * Unregisters all handler methods on a registered {@code object}. + * + * @param object object whose handler methods should be unregistered. + * @throws IllegalArgumentException if the object was not previously registered. + */ + public void unregister(Object object) { + Multimap, EventHandler> methodsInListener = finder.findAllHandlers(object); + for (Entry, Collection> entry : methodsInListener.asMap().entrySet()) { + Set currentHandlers = getHandlersForEventType(entry.getKey()); + Collection eventMethodsInListener = entry.getValue(); + + if (currentHandlers == null || !currentHandlers.containsAll(entry.getValue())) { + throw new IllegalArgumentException( + "missing event handler for an annotated method. Is " + object + " registered?"); + } + currentHandlers.removeAll(eventMethodsInListener); + } + } + + /** + * Posts an event to all registered handlers. This method will return + * successfully after the event has been posted to all handlers, and + * regardless of any exceptions thrown by handlers. + * + *

If no handlers have been subscribed for {@code event}'s class, and + * {@code event} is not already a {@link DeadEvent}, it will be wrapped in a + * DeadEvent and reposted. + * + * @param event event to post. + */ + @SuppressWarnings("deprecation") // only deprecated for external subclasses + public void post(Object event) { + Set> dispatchTypes = flattenHierarchy(event.getClass()); + + boolean dispatched = false; + for (Class eventType : dispatchTypes) { + Set wrappers = getHandlersForEventType(eventType); + + if (wrappers != null && !wrappers.isEmpty()) { + dispatched = true; + for (EventHandler wrapper : wrappers) { + enqueueEvent(event, wrapper); + } + } + } + + if (!dispatched && !(event instanceof DeadEvent)) { + post(new DeadEvent(this, event)); + } + + dispatchQueuedEvents(); + } + + /** + * Queue the {@code event} for dispatch during + * {@link #dispatchQueuedEvents()}. Events are queued in-order of occurrence + * so they can be dispatched in the same order. + */ + void enqueueEvent(Object event, EventHandler handler) { + eventsToDispatch.get().offer(new EventWithHandler(event, handler)); + } + + /** + * Drain the queue of events to be dispatched. As the queue is being drained, + * new events may be posted to the end of the queue. + * + * @deprecated This method should not be overridden outside of the eventbus package. It is + * scheduled for removal in Guava 14.0. + */ + @Deprecated + protected void dispatchQueuedEvents() { + // don't dispatch if we're already dispatching, that would allow reentrancy + // and out-of-order events. Instead, leave the events to be dispatched + // after the in-progress dispatch is complete. + if (isDispatching.get()) { + return; + } + + isDispatching.set(true); + try { + while (true) { + EventWithHandler eventWithHandler = eventsToDispatch.get().poll(); + if (eventWithHandler == null) { + break; + } + + dispatch(eventWithHandler.event, eventWithHandler.handler); + } + } finally { + isDispatching.set(false); + } + } + + /** + * Dispatches {@code event} to the handler in {@code wrapper}. This method + * is an appropriate override point for subclasses that wish to make + * event delivery asynchronous. + * + * @param event event to dispatch. + * @param wrapper wrapper that will call the handler. + */ + void dispatch(Object event, EventHandler wrapper) { + try { + wrapper.handleEvent(event); + } catch (InvocationTargetException e) { + logger.log(Level.SEVERE, + "Could not dispatch event: " + event + " to handler " + wrapper, e); + } + } + + /** + * Retrieves a mutable set of the currently registered handlers for + * {@code type}. If no handlers are currently registered for {@code type}, + * this method may either return {@code null} or an empty set. + * + * @param type type of handlers to retrieve. + * @return currently registered handlers, or {@code null}. + */ + Set getHandlersForEventType(Class type) { + return handlersByType.get(type); + } + + /** + * Creates a new Set for insertion into the handler map. This is provided + * as an override point for subclasses. The returned set should support + * concurrent access. + * + * @return a new, mutable set for handlers. + */ + Set newHandlerSet() { + return new CopyOnWriteArraySet(); + } + + /** + * Flattens a class's type hierarchy into a set of Class objects. The set + * will include all superclasses (transitively), and all interfaces + * implemented by these superclasses. + * + * @param concreteClass class whose type hierarchy will be retrieved. + * @return {@code clazz}'s complete type hierarchy, flattened and uniqued. + */ + @VisibleForTesting + Set> flattenHierarchy(Class concreteClass) { + try { + return flattenHierarchyCache.get(concreteClass); + } catch (ExecutionException e) { + throw Throwables.propagate(e.getCause()); + } + } + + /** simple struct representing an event and it's handler */ + static class EventWithHandler { + final Object event; + final EventHandler handler; + public EventWithHandler(Object event, EventHandler handler) { + this.event = event; + this.handler = handler; + } + } +} diff --git a/guava/src/com/google/common/eventbus/EventHandler.java b/guava/src/com/google/common/eventbus/EventHandler.java new file mode 100644 index 0000000..a072462 --- /dev/null +++ b/guava/src/com/google/common/eventbus/EventHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.base.Preconditions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Wraps a single-argument 'handler' method on a specific object. + * + *

This class only verifies the suitability of the method and event type if + * something fails. Callers are expected to verify their uses of this class. + * + *

Two EventHandlers are equivalent when they refer to the same method on the + * same object (not class). This property is used to ensure that no handler + * method is registered more than once. + * + * @author Cliff Biffle + */ +class EventHandler { + + /** Object sporting the handler method. */ + private final Object target; + /** Handler method. */ + private final Method method; + + /** + * Creates a new EventHandler to wrap {@code method} on @{code target}. + * + * @param target object to which the method applies. + * @param method handler method. + */ + EventHandler(Object target, Method method) { + Preconditions.checkNotNull(target, + "EventHandler target cannot be null."); + Preconditions.checkNotNull(method, "EventHandler method cannot be null."); + + this.target = target; + this.method = method; + method.setAccessible(true); + } + + /** + * Invokes the wrapped handler method to handle {@code event}. + * + * @param event event to handle + * @throws InvocationTargetException if the wrapped method throws any + * {@link Throwable} that is not an {@link Error} ({@code Error}s are + * propagated as-is). + */ + public void handleEvent(Object event) throws InvocationTargetException { + try { + method.invoke(target, new Object[] { event }); + } catch (IllegalArgumentException e) { + throw new Error("Method rejected target/argument: " + event, e); + } catch (IllegalAccessException e) { + throw new Error("Method became inaccessible: " + event, e); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof Error) { + throw (Error) e.getCause(); + } + throw e; + } + } + + @Override public String toString() { + return "[wrapper " + method + "]"; + } + + @Override public int hashCode() { + final int PRIME = 31; + return (PRIME + method.hashCode()) * PRIME + target.hashCode(); + } + + @Override public boolean equals(Object obj) { + if(this == obj) { + return true; + } + + if(obj == null) { + return false; + } + + if(getClass() != obj.getClass()) { + return false; + } + + final EventHandler other = (EventHandler) obj; + + return method.equals(other.method) && target == other.target; + } + +} diff --git a/guava/src/com/google/common/eventbus/HandlerFindingStrategy.java b/guava/src/com/google/common/eventbus/HandlerFindingStrategy.java new file mode 100644 index 0000000..7144203 --- /dev/null +++ b/guava/src/com/google/common/eventbus/HandlerFindingStrategy.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.collect.Multimap; + +/** + * A method for finding event handler methods in objects, for use by + * {@link EventBus}. + * + * @author Cliff Biffle + */ +interface HandlerFindingStrategy { + + /** + * Finds all suitable event handler methods in {@code source}, organizes them + * by the type of event they handle, and wraps them in {@link EventHandler}s. + * + * @param source object whose handlers are desired. + * @return EventHandler objects for each handler method, organized by event + * type. + * + * @throws IllegalArgumentException if {@code source} is not appropriate for + * this strategy (in ways that this interface does not define). + */ + Multimap, EventHandler> findAllHandlers(Object source); + +} diff --git a/guava/src/com/google/common/eventbus/Subscribe.java b/guava/src/com/google/common/eventbus/Subscribe.java new file mode 100644 index 0000000..34cb587 --- /dev/null +++ b/guava/src/com/google/common/eventbus/Subscribe.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as an event handler, as used by + * {@link AnnotatedHandlerFinder} and {@link EventBus}. + * + *

The type of event will be indicated by the method's first (and only) + * parameter. If this annotation is applied to methods with zero parameters, + * or more than one parameter, the object containing the method will not be able + * to register for event delivery from the {@link EventBus}. + * + *

Unless also annotated with @{@link AllowConcurrentEvents}, event handler + * methods will be invoked serially by each event bus that they are registered + * with. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Beta +public @interface Subscribe { +} diff --git a/guava/src/com/google/common/eventbus/SynchronizedEventHandler.java b/guava/src/com/google/common/eventbus/SynchronizedEventHandler.java new file mode 100644 index 0000000..2792704 --- /dev/null +++ b/guava/src/com/google/common/eventbus/SynchronizedEventHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Wraps a single-argument 'handler' method on a specific object, and ensures + * that only one thread may enter the method at a time. + * + *

Beyond synchronization, this class behaves identically to + * {@link EventHandler}. + * + * @author Cliff Biffle + */ +class SynchronizedEventHandler extends EventHandler { + /** + * Creates a new SynchronizedEventHandler to wrap {@code method} on + * {@code target}. + * + * @param target object to which the method applies. + * @param method handler method. + */ + public SynchronizedEventHandler(Object target, Method method) { + super(target, method); + } + + @Override public synchronized void handleEvent(Object event) + throws InvocationTargetException { + super.handleEvent(event); + } + +} diff --git a/guava/src/com/google/common/eventbus/package-info.java b/guava/src/com/google/common/eventbus/package-info.java new file mode 100644 index 0000000..28637cd --- /dev/null +++ b/guava/src/com/google/common/eventbus/package-info.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The EventBus allows publish-subscribe-style communication between components + * without requiring the components to explicitly register with one another + * (and thus be aware of each other). It is designed exclusively to replace + * traditional Java in-process event distribution using explicit registration. + * It is not a general-purpose publish-subscribe system, nor is it + * intended for interprocess communication. + * + *

See the Guava User Guide article on + * {@code EventBus}. + * + *

One-Minute Guide

+ * + * Converting an existing EventListener-based system to use the EventBus is + * easy. + * + *

For Listeners

+ * To listen for a specific flavor of event (say, a CustomerChangeEvent)... + *
    + *
  • ...in traditional Java events: implement an interface + * defined with the event — such as CustomerChangeEventListener.
  • + *
  • ...with EventBus: create a method that accepts + * CustomerChangeEvent as its sole argument, and mark it with the + * {@link com.google.common.eventbus.Subscribe} annotation.
  • + *
+ * + *

To register your listener methods with the event producers... + *

    + *
  • ...in traditional Java events: pass your object to each + * producer's {@code registerCustomerChangeEventListener} method. These + * methods are rarely defined in common interfaces, so in addition to + * knowing every possible producer, you must also know its type.
  • + *
  • ...with EventBus: pass your object to the + * {@link com.google.common.eventbus.EventBus#register(Object)} method on an + * EventBus. You'll need to + * make sure that your object shares an EventBus instance with the event + * producers.
  • + *
+ * + *

To listen for a common event supertype (such as EventObject or Object)... + *

    + *
  • ...in traditional Java events: not easy.
  • + *
  • ...with EventBus: events are automatically dispatched to + * listeners of any supertype, allowing listeners for interface types + * or "wildcard listeners" for Object.
  • + *
+ * + *

To listen for and detect events that were dispatched without listeners... + *

    + *
  • ...in traditional Java events: add code to each + * event-dispatching method (perhaps using AOP).
  • + *
  • ...with EventBus: subscribe to {@link + * com.google.common.eventbus.DeadEvent}. The + * EventBus will notify you of any events that were posted but not + * delivered. (Handy for debugging.)
  • + *
+ * + *

For Producers

+ * To keep track of listeners to your events... + *
    + *
  • ...in traditional Java events: write code to manage + * a list of listeners to your object, including synchronization, or use a + * utility class like EventListenerList.
  • + *
  • ...with EventBus: EventBus does this for you.
  • + *
+ * + *

To dispatch an event to listeners... + *

    + *
  • ...in traditional Java events: write a method to + * dispatch events to each event listener, including error isolation and + * (if desired) asynchronicity.
  • + *
  • ...with EventBus: pass the event object to an EventBus's + * {@link com.google.common.eventbus.EventBus#post(Object)} method.
  • + *
+ * + *

Glossary

+ * + * The EventBus system and code use the following terms to discuss event + * distribution: + *
+ *
Event
Any object that may be posted to a bus.
+ *
Subscribing
The act of registering a listener with an + * EventBus, so that its handler methods will receive events.
+ *
Listener
An object that wishes to receive events, by exposing + * handler methods. + *
Handler method
A public method that the EventBus should use to + * deliver posted events. Handler methods are marked by the + * {@link com.google.common.eventbus.Subscribe} annotation.
+ *
Posting an event
Making the event available to any + * listeners through the EventBus. + *
+ * + *

FAQ

+ *

Why must I create my own Event Bus, rather than using a singleton?

+ * + * The Event Bus doesn't specify how you use it; there's nothing stopping your + * application from having separate EventBus instances for each component, or + * using separate instances to separate events by context or topic. This also + * makes it trivial to set up and tear down EventBus objects in your tests. + * + *

Of course, if you'd like to have a process-wide EventBus singleton, + * there's nothing stopping you from doing it that way. Simply have your + * container (such as Guice) create the EventBus as a singleton at global scope + * (or stash it in a static field, if you're into that sort of thing). + * + *

In short, the EventBus is not a singleton because we'd rather not make + * that decision for you. Use it how you like. + * + *

Why use an annotation to mark handler methods, rather than requiring the + * listener to implement an interface?

+ * We feel that the Event Bus's {@code @Subscribe} annotation conveys your + * intentions just as explicitly as implementing an interface (or perhaps more + * so), while leaving you free to place event handler methods wherever you wish + * and give them intention-revealing names. + * + *

Traditional Java Events use a listener interface which typically sports + * only a handful of methods -- typically one. This has a number of + * disadvantages: + *

    + *
  • Any one class can only implement a single response to a given event. + *
  • Listener interface methods may conflict. + *
  • The method must be named after the event (e.g. {@code + * handleChangeEvent}), rather than its purpose (e.g. {@code + * recordChangeInJournal}). + *
  • Each event usually has its own interface, without a common parent + * interface for a family of events (e.g. all UI events). + *
+ * + *

The difficulties in implementing this cleanly has given rise to a pattern, + * particularly common in Swing apps, of using tiny anonymous classes to + * implement event listener interfaces. + * + *

Compare these two cases:

+ *   class ChangeRecorder {
+ *     void setCustomer(Customer cust) {
+ *       cust.addChangeListener(new ChangeListener() {
+ *         void customerChanged(ChangeEvent e) {
+ *           recordChange(e.getChange());
+ *         }
+ *       };
+ *     }
+ *   }
+ *
+ *   // Class is typically registered by the container.
+ *   class EventBusChangeRecorder {
+ *     @Subscribe void recordCustomerChange(ChangeEvent e) {
+ *       recordChange(e.getChange());
+ *     }
+ *   }
+ * + * The intent is actually clearer in the second case: there's less noise code, + * and the event handler has a clear and meaningful name. + * + *

What about a generic {@code Handler} interface?

+ * Some have proposed a generic {@code Handler} interface for EventBus + * listeners. This runs into issues with Java's use of type erasure, not to + * mention problems in usability. + * + *

Let's say the interface looked something like the following:

   {@code
+ *   interface Handler {
+ *     void handleEvent(T event);
+ *   }}
+ * + * Due to erasure, no single class can implement a generic interface more than + * once with different type parameters. This is a giant step backwards from + * traditional Java Events, where even if {@code actionPerformed} and {@code + * keyPressed} aren't very meaningful names, at least you can implement both + * methods! + * + *

Doesn't EventBus destroy static typing and eliminate automated + * refactoring support?

+ * Some have freaked out about EventBus's {@code register(Object)} and {@code + * post(Object)} methods' use of the {@code Object} type. + * + *

{@code Object} is used here for a good reason: the Event Bus library + * places no restrictions on the types of either your event listeners (as in + * {@code register(Object)}) or the events themselves (in {@code post(Object)}). + * + *

Event handler methods, on the other hand, must explicitly declare their + * argument type -- the type of event desired (or one of its supertypes). Thus, + * searching for references to an event class will instantly find all handler + * methods for that event, and renaming the type will affect all handler methods + * within view of your IDE (and any code that creates the event). + * + *

It's true that you can rename your {@code @Subscribed} event handler + * methods at will; Event Bus will not stop this or do anything to propagate the + * rename because, to Event Bus, the names of your handler methods are + * irrelevant. Test code that calls the methods directly, of course, will be + * affected by your renaming -- but that's what your refactoring tools are for. + * + *

What happens if I {@code register} a listener without any handler + * methods?

+ * Nothing at all. + * + *

The Event Bus was designed to integrate with containers and module + * systems, with Guice as the prototypical example. In these cases, it's + * convenient to have the container/factory/environment pass every + * created object to an EventBus's {@code register(Object)} method. + * + *

This way, any object created by the container/factory/environment can + * hook into the system's event model simply by exposing handler methods. + * + *

What Event Bus problems can be detected at compile time?

+ * Any problem that can be unambiguously detected by Java's type system. For + * example, defining a handler method for a nonexistent event type. + * + *

What Event Bus problems can be detected immediately at registration?

+ * Immediately upon invoking {@code register(Object)} , the listener being + * registered is checked for the well-formedness of its handler methods. + * Specifically, any methods marked with {@code @Subscribe} must take only a + * single argument. + * + *

Any violations of this rule will cause an {@code IllegalArgumentException} + * to be thrown. + * + *

(This check could be moved to compile-time using APT, a solution we're + * researching.) + * + *

What Event Bus problems may only be detected later, at runtime?

+ * If a component posts events with no registered listeners, it may + * indicate an error (typically an indication that you missed a + * {@code @Subscribe} annotation, or that the listening component is not loaded). + * + *

(Note that this is not necessarily indicative of a problem. There + * are many cases where an application will deliberately ignore a posted event, + * particularly if the event is coming from code you don't control.) + * + *

To handle such events, register a handler method for the {@code DeadEvent} + * class. Whenever EventBus receives an event with no registered handlers, it + * will turn it into a {@code DeadEvent} and pass it your way -- allowing you to + * log it or otherwise recover. + * + *

How do I test event listeners and their handler methods?

+ * Because handler methods on your listener classes are normal methods, you can + * simply call them from your test code to simulate the EventBus. + */ +package com.google.common.eventbus; diff --git a/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java b/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java new file mode 100644 index 0000000..e0b074f --- /dev/null +++ b/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.hash; + +import java.nio.charset.Charset; + +/** + * An abstract composition of multiple hash functions. {@linkplain #newHasher()} delegates to the + * {@code Hasher} objects of the delegate hash functions, and in the end, they are used by + * {@linkplain #makeHash(Hasher[])} that constructs the final {@code HashCode}. + * + * @author Dimitris Andreou + */ +abstract class AbstractCompositeHashFunction extends AbstractStreamingHashFunction { + final HashFunction[] functions; + + AbstractCompositeHashFunction(HashFunction... functions) { + this.functions = functions; + } + + /** + * Constructs a {@code HashCode} from the {@code Hasher} objects of the functions. Each of them + * has consumed the entire input and they are ready to output a {@code HashCode}. The order of + * the hashers are the same order as the functions given to the constructor. + */ + // this could be cleaner if it passed HashCode[], but that would create yet another array... + /* protected */ abstract HashCode makeHash(Hasher[] hashers); + + @Override + public Hasher newHasher() { + final Hasher[] hashers = new Hasher[functions.length]; + for (int i = 0; i < hashers.length; i++) { + hashers[i] = functions[i].newHasher(); + } + return new Hasher() { + @Override public Hasher putByte(byte b) { + for (Hasher hasher : hashers) { + hasher.putByte(b); + } + return this; + } + + @Override public Hasher putBytes(byte[] bytes) { + for (Hasher hasher : hashers) { + hasher.putBytes(bytes); + } + return this; + } + + @Override public Hasher putBytes(byte[] bytes, int off, int len) { + for (Hasher hasher : hashers) { + hasher.putBytes(bytes, off, len); + } + return this; + } + + @Override public Hasher putShort(short s) { + for (Hasher hasher : hashers) { + hasher.putShort(s); + } + return this; + } + + @Override public Hasher putInt(int i) { + for (Hasher hasher : hashers) { + hasher.putInt(i); + } + return this; + } + + @Override public Hasher putLong(long l) { + for (Hasher hasher : hashers) { + hasher.putLong(l); + } + return this; + } + + @Override public Hasher putFloat(float f) { + for (Hasher hasher : hashers) { + hasher.putFloat(f); + } + return this; + } + + @Override public Hasher putDouble(double d) { + for (Hasher hasher : hashers) { + hasher.putDouble(d); + } + return this; + } + + @Override public Hasher putBoolean(boolean b) { + for (Hasher hasher : hashers) { + hasher.putBoolean(b); + } + return this; + } + + @Override public Hasher putChar(char c) { + for (Hasher hasher : hashers) { + hasher.putChar(c); + } + return this; + } + + @Override public Hasher putString(CharSequence chars) { + for (Hasher hasher : hashers) { + hasher.putString(chars); + } + return this; + } + + @Override public Hasher putString(CharSequence chars, Charset charset) { + for (Hasher hasher : hashers) { + hasher.putString(chars, charset); + } + return this; + } + + @Override public Hasher putObject(T instance, Funnel funnel) { + for (Hasher hasher : hashers) { + hasher.putObject(instance, funnel); + } + return this; + } + + @Override public HashCode hash() { + return makeHash(hashers); + } + }; + } + + private static final long serialVersionUID = 0L; +} diff --git a/guava/src/com/google/common/hash/AbstractHasher.java b/guava/src/com/google/common/hash/AbstractHasher.java new file mode 100644 index 0000000..f60f928 --- /dev/null +++ b/guava/src/com/google/common/hash/AbstractHasher.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import java.nio.charset.Charset; + +/** + * An abstract hasher, implementing {@link #putBoolean(boolean)}, {@link #putDouble(double)}, + * {@link #putFloat(float)}, {@link #putString(CharSequence)}, and + * {@link #putString(CharSequence, Charset)} as prescribed by {@link Hasher}. + * + * @author Dimitris Andreou + */ +abstract class AbstractHasher implements Hasher { + @Override public final Hasher putBoolean(boolean b) { + return putByte(b ? (byte) 1 : (byte) 0); + } + + @Override public final Hasher putDouble(double d) { + return putLong(Double.doubleToRawLongBits(d)); + } + + @Override public final Hasher putFloat(float f) { + return putInt(Float.floatToRawIntBits(f)); + } + + @Override public Hasher putString(CharSequence charSequence) { + for (int i = 0, len = charSequence.length(); i < len; i++) { + putChar(charSequence.charAt(i)); + } + return this; + } + + @Override public Hasher putString(CharSequence charSequence, Charset charset) { + return putBytes(charSequence.toString().getBytes(charset)); + } +} diff --git a/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java b/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java new file mode 100644 index 0000000..28a9c92 --- /dev/null +++ b/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.hash; + +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Skeleton implementation of {@link HashFunction}, appropriate for non-streaming algorithms. + * All the hash computation done using {@linkplain #newHasher()} are delegated to the {@linkplain + * #hashBytes(byte[], int, int)} method. + * + * @author Dimitris Andreou + */ +abstract class AbstractNonStreamingHashFunction implements HashFunction { + @Override + public Hasher newHasher() { + return new BufferingHasher(32); + } + + @Override + public Hasher newHasher(int expectedInputSize) { + Preconditions.checkArgument(expectedInputSize >= 0); + return new BufferingHasher(expectedInputSize); + } + + @Override public HashCode hashString(CharSequence input) { + int len = input.length(); + Hasher hasher = newHasher(len * 2); + for (int i = 0; i < len; i++) { + hasher.putChar(input.charAt(i)); + } + return hasher.hash(); + } + + @Override public HashCode hashString(CharSequence input, Charset charset) { + return hashBytes(input.toString().getBytes(charset)); + } + + @Override public HashCode hashInt(int input) { + return newHasher(4).putInt(input).hash(); + } + + @Override public HashCode hashLong(long input) { + return newHasher(8).putLong(input).hash(); + } + + @Override public HashCode hashBytes(byte[] input) { + return hashBytes(input, 0, input.length); + } + + /** + * In-memory stream-based implementation of Hasher. + */ + private final class BufferingHasher extends AbstractHasher { + final ExposedByteArrayOutputStream stream; + static final int BOTTOM_BYTE = 0xFF; + + BufferingHasher(int expectedInputSize) { + this.stream = new ExposedByteArrayOutputStream(expectedInputSize); + } + + @Override + public Hasher putByte(byte b) { + stream.write(b); + return this; + } + + @Override + public Hasher putBytes(byte[] bytes) { + try { + stream.write(bytes); + } catch (IOException e) { + throw Throwables.propagate(e); + } + return this; + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + stream.write(bytes, off, len); + return this; + } + + @Override + public Hasher putShort(short s) { + stream.write(s & BOTTOM_BYTE); + stream.write((s >>> 8) & BOTTOM_BYTE); + return this; + } + + @Override + public Hasher putInt(int i) { + stream.write(i & BOTTOM_BYTE); + stream.write((i >>> 8) & BOTTOM_BYTE); + stream.write((i >>> 16) & BOTTOM_BYTE); + stream.write((i >>> 24) & BOTTOM_BYTE); + return this; + } + + @Override + public Hasher putLong(long l) { + for (int i = 0; i < 64; i += 8) { + stream.write((byte) ((l >>> i) & BOTTOM_BYTE)); + } + return this; + } + + @Override + public Hasher putChar(char c) { + stream.write(c & BOTTOM_BYTE); + stream.write((c >>> 8) & BOTTOM_BYTE); + return this; + } + + @Override + public Hasher putObject(T instance, Funnel funnel) { + funnel.funnel(instance, this); + return this; + } + + @Override + public HashCode hash() { + return hashBytes(stream.byteArray(), 0, stream.length()); + } + } + + // Just to access the byte[] without introducing an unnecessary copy + private static final class ExposedByteArrayOutputStream extends ByteArrayOutputStream { + ExposedByteArrayOutputStream(int expectedInputSize) { + super(expectedInputSize); + } + byte[] byteArray() { + return buf; + } + int length() { + return count; + } + } +} diff --git a/guava/src/com/google/common/hash/AbstractStreamingHashFunction.java b/guava/src/com/google/common/hash/AbstractStreamingHashFunction.java new file mode 100644 index 0000000..a5ba342 --- /dev/null +++ b/guava/src/com/google/common/hash/AbstractStreamingHashFunction.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Preconditions; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; + +/** + * Skeleton implementation of {@link HashFunction}. Provides default implementations which + * invokes the appropriate method on {@link #newHasher()}, then return the result of + * {@link Hasher#hash}. + * + *

Invocations of {@link #newHasher(int)} also delegate to {@linkplain #newHasher()}, ignoring + * the expected input size parameter. + * + * @author Kevin Bourrillion + */ +abstract class AbstractStreamingHashFunction implements HashFunction { + @Override public HashCode hashString(CharSequence input) { + return newHasher().putString(input).hash(); + } + + @Override public HashCode hashString(CharSequence input, Charset charset) { + return newHasher().putString(input, charset).hash(); + } + + @Override public HashCode hashInt(int input) { + return newHasher().putInt(input).hash(); + } + + @Override public HashCode hashLong(long input) { + return newHasher().putLong(input).hash(); + } + + @Override public HashCode hashBytes(byte[] input) { + return newHasher().putBytes(input).hash(); + } + + @Override public HashCode hashBytes(byte[] input, int off, int len) { + return newHasher().putBytes(input, off, len).hash(); + } + + @Override public Hasher newHasher(int expectedInputSize) { + Preconditions.checkArgument(expectedInputSize >= 0); + return newHasher(); + } + + /** + * A convenience base class for implementors of {@code Hasher}; handles accumulating data + * until an entire "chunk" (of implementation-dependent length) is ready to be hashed. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + */ + // TODO(kevinb): this class still needs some design-and-document-for-inheritance love + protected static abstract class AbstractStreamingHasher extends AbstractHasher { + /** Buffer via which we pass data to the hash algorithm (the implementor) */ + private final ByteBuffer buffer; + + /** Number of bytes to be filled before process() invocation(s). */ + private final int bufferSize; + + /** Number of bytes processed per process() invocation. */ + private final int chunkSize; + + /** + * Constructor for use by subclasses. This hasher instance will process chunks of the specified + * size. + * + * @param chunkSize the number of bytes available per {@link #process(ByteBuffer)} invocation; + * must be at least 4 + */ + protected AbstractStreamingHasher(int chunkSize) { + this(chunkSize, chunkSize); + } + + /** + * Constructor for use by subclasses. This hasher instance will process chunks of the specified + * size, using an internal buffer of {@code bufferSize} size, which must be a multiple of + * {@code chunkSize}. + * + * @param chunkSize the number of bytes available per {@link #process(ByteBuffer)} invocation; + * must be at least 4 + * @param bufferSize the size of the internal buffer. Must be a multiple of chunkSize + */ + protected AbstractStreamingHasher(int chunkSize, int bufferSize) { + // TODO(kevinb): check more preconditions (as bufferSize >= chunkSize) if this is ever public + checkArgument(bufferSize % chunkSize == 0); + + // TODO(user): benchmark performance difference with longer buffer + this.buffer = ByteBuffer + .allocate(bufferSize + 7) // always space for a single primitive + .order(ByteOrder.LITTLE_ENDIAN); + this.bufferSize = bufferSize; + this.chunkSize = chunkSize; + } + + /** + * Processes the available bytes of the buffer (at most {@code chunk} bytes). + */ + protected abstract void process(ByteBuffer bb); + + /** + * This is invoked for the last bytes of the input, which are not enough to + * fill a whole chunk. The passed {@code ByteBuffer} is guaranteed to be + * non-empty. + * + *

This implementation simply pads with zeros and delegates to + * {@link #process(ByteBuffer)}. + */ + protected void processRemaining(ByteBuffer bb) { + bb.position(bb.limit()); // move at the end + bb.limit(chunkSize + 7); // get ready to pad with longs + while (bb.position() < chunkSize) { + bb.putLong(0); + } + bb.limit(chunkSize); + bb.flip(); + process(bb); + } + + @Override + public final Hasher putBytes(byte[] bytes) { + return putBytes(bytes, 0, bytes.length); + } + + @Override + public final Hasher putBytes(byte[] bytes, int off, int len) { + return putBytes(ByteBuffer.wrap(bytes, off, len).order(ByteOrder.LITTLE_ENDIAN)); + } + + private final Hasher putBytes(ByteBuffer readBuffer) { + // If we have room for all of it, this is easy + if (readBuffer.remaining() <= buffer.remaining()) { + buffer.put(readBuffer); + munchIfFull(); + return this; + } + + // First add just enough to fill buffer size, and munch that + int bytesToCopy = bufferSize - buffer.position(); + for (int i = 0; i < bytesToCopy; i++) { + buffer.put(readBuffer.get()); + } + munch(); // buffer becomes empty here, since chunkSize divides bufferSize + + // Now process directly from the rest of the input buffer + while (readBuffer.remaining() >= chunkSize) { + process(readBuffer); + } + + // Finally stick the remainder back in our usual buffer + buffer.put(readBuffer); + return this; + } + + @Override + public final Hasher putString(CharSequence charSequence) { + for (int i = 0; i < charSequence.length(); i++) { + putChar(charSequence.charAt(i)); + } + return this; + } + + @Override + public final Hasher putByte(byte b) { + buffer.put(b); + munchIfFull(); + return this; + } + + @Override + public final Hasher putShort(short s) { + buffer.putShort(s); + munchIfFull(); + return this; + } + + @Override + public final Hasher putChar(char c) { + buffer.putChar(c); + munchIfFull(); + return this; + } + + @Override + public final Hasher putInt(int i) { + buffer.putInt(i); + munchIfFull(); + return this; + } + + @Override + public final Hasher putLong(long l) { + buffer.putLong(l); + munchIfFull(); + return this; + } + + @Override + public final Hasher putObject(T instance, Funnel funnel) { + funnel.funnel(instance, this); + return this; + } + + @Override + public final HashCode hash() { + munch(); + buffer.flip(); + if (buffer.remaining() > 0) { + processRemaining(buffer); + } + return makeHash(); + } + + abstract HashCode makeHash(); + + // Process pent-up data in chunks + private void munchIfFull() { + if (buffer.remaining() < 8) { + // buffer is full; not enough room for a primitive. We have at least one full chunk. + munch(); + } + } + + private void munch() { + buffer.flip(); + while (buffer.remaining() >= chunkSize) { + // we could limit the buffer to ensure process() does not read more than + // chunkSize number of bytes, but we trust the implementations + process(buffer); + } + buffer.compact(); // preserve any remaining data that do not make a full chunk + } + } +} diff --git a/guava/src/com/google/common/hash/BloomFilter.java b/guava/src/com/google/common/hash/BloomFilter.java new file mode 100644 index 0000000..8fe6ba7 --- /dev/null +++ b/guava/src/com/google/common/hash/BloomFilter.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.hash.BloomFilterStrategies.BitArray; + +import java.io.Serializable; + +/** + * A Bloom filter for instances of {@code T}. A Bloom filter offers an approximate containment test + * with one-sided error: if it claims that an element is contained in it, this might be in error, + * but if it claims that an element is not contained in it, then this is definitely true. + * + *

If you are unfamiliar with Bloom filters, this nice + * tutorial may help you understand + * how they work. + * + * + * @param the type of instances that the {@code BloomFilter} accepts + * @author Dimitris Andreou + * @author Kevin Bourrillion + * @since 11.0 + */ +@Beta +public final class BloomFilter implements Serializable { + /** + * A strategy to translate T instances, to {@code numHashFunctions} bit indexes. + * + *

Implementations should be collections of pure functions (i.e. stateless). + */ + interface Strategy extends java.io.Serializable { + + /** + * Sets {@code numHashFunctions} bits of the given bit array, by hashing a user element. + * + *

Returns whether any bits changed as a result of this operation. + */ + boolean put(T object, Funnel funnel, int numHashFunctions, BitArray bits); + + /** + * Queries {@code numHashFunctions} bits of the given bit array, by hashing a user element; + * returns {@code true} if and only if all selected bits are set. + */ + boolean mightContain( + T object, Funnel funnel, int numHashFunctions, BitArray bits); + + /** + * Identifier used to encode this strategy, when marshalled as part of a BloomFilter. + * Only values in the [-128, 127] range are valid for the compact serial form. + * Non-negative values are reserved for enums defined in BloomFilterStrategies; + * negative values are reserved for any custom, stateful strategy we may define + * (e.g. any kind of strategy that would depend on user input). + */ + int ordinal(); + } + + /** The bit set of the BloomFilter (not necessarily power of 2!)*/ + private final BitArray bits; + + /** Number of hashes per element */ + private final int numHashFunctions; + + /** The funnel to translate Ts to bytes */ + private final Funnel funnel; + + /** + * The strategy we employ to map an element T to {@code numHashFunctions} bit indexes. + */ + private final Strategy strategy; + + /** + * Creates a BloomFilter. + */ + private BloomFilter(BitArray bits, int numHashFunctions, Funnel funnel, + Strategy strategy) { + Preconditions.checkArgument(numHashFunctions > 0, "numHashFunctions zero or negative"); + this.bits = checkNotNull(bits); + this.numHashFunctions = numHashFunctions; + this.funnel = checkNotNull(funnel); + this.strategy = strategy; + + /* + * This only exists to forbid BFs that cannot use the compact persistent representation. + * If it ever throws, at a user who was not intending to use that representation, we should + * reconsider + */ + if (numHashFunctions > 255) { + throw new AssertionError("Currently we don't allow BloomFilters that would use more than" + + "255 hash functions, please contact the guava team"); + } + } + + /** + * Creates a new {@code BloomFilter} that's a copy of this instance. The new instance is equal to + * this instance but shares no mutable state. + * + * @since 12.0 + */ + public BloomFilter copy() { + return new BloomFilter(bits.copy(), numHashFunctions, funnel, strategy); + } + + /** + * Returns {@code true} if the element might have been put in this Bloom filter, + * {@code false} if this is definitely not the case. + */ + public boolean mightContain(T object) { + return strategy.mightContain(object, funnel, numHashFunctions, bits); + } + + /** + * Puts an element into this {@code BloomFilter}. Ensures that subsequent invocations of + * {@link #mightContain(Object)} with the same element will always return {@code true}. + * + * @return true if the bloom filter's bits changed as a result of this operation. If the bits + * changed, this is definitely the first time {@code object} has been added to the + * filter. If the bits haven't changed, this might be the first time {@code object} + * has been added to the filter. Note that {@code put(t)} always returns the + * opposite result to what {@code mightContain(t)} would have returned at the time + * it is called." + * @since 12.0 (present in 11.0 with {@code void} return type}) + */ + public boolean put(T object) { + return strategy.put(object, funnel, numHashFunctions, bits); + } + + /** + * Returns the probability that {@linkplain #mightContain(Object)} will erroneously return + * {@code true} for an object that has not actually been put in the {@code BloomFilter}. + * + *

Ideally, this number should be close to the {@code falsePositiveProbability} parameter + * passed in {@linkplain #create(Funnel, int, double)}, or smaller. If it is + * significantly higher, it is usually the case that too many elements (more than + * expected) have been put in the {@code BloomFilter}, degenerating it. + */ + public double expectedFalsePositiveProbability() { + return Math.pow((double) bits.bitCount() / bits.size(), numHashFunctions); + } + + /** + * {@inheritDoc} + * + *

This implementation uses reference equality to compare funnels. + */ + @Override public boolean equals(Object o) { + if (o instanceof BloomFilter) { + BloomFilter that = (BloomFilter) o; + return this.numHashFunctions == that.numHashFunctions + && this.bits.equals(that.bits) + && this.funnel == that.funnel + && this.strategy == that.strategy; + } + return false; + } + + @Override public int hashCode() { + return bits.hashCode(); + } + + @VisibleForTesting int getHashCount() { + return numHashFunctions; + } + + /** + * Creates a {@code Builder} of a {@link BloomFilter BloomFilter}, with the expected number + * of insertions and expected false positive probability. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements + * than specified, will result in its saturation, and a sharp deterioration of its + * false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided + * {@code Funnel} is. + * + *

It is recommended the funnel is implemented as a Java enum. This has the benefit of ensuring + * proper serialization and deserialization, which is important since {@link #equals} also relies + * on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed + * {@code BloomFilter}; must be positive + * @param falsePositiveProbability the desired false positive probability (must be positive and + * less than 1.0) + * @return a {@code BloomFilter} + */ + public static BloomFilter create(Funnel funnel, int expectedInsertions /* n */, + double falsePositiveProbability) { + checkNotNull(funnel); + checkArgument(expectedInsertions >= 0, "Expected insertions cannot be negative"); + checkArgument(falsePositiveProbability > 0.0 & falsePositiveProbability < 1.0, + "False positive probability in (0.0, 1.0)"); + if (expectedInsertions == 0) { + expectedInsertions = 1; + } + /* + * andreou: I wanted to put a warning in the javadoc about tiny fpp values, + * since the resulting size is proportional to -log(p), but there is not + * much of a point after all, e.g. optimalM(1000, 0.0000000000000001) = 76680 + * which is less that 10kb. Who cares! + */ + int numBits = optimalNumOfBits(expectedInsertions, falsePositiveProbability); + int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); + return new BloomFilter(new BitArray(numBits), numHashFunctions, funnel, + BloomFilterStrategies.MURMUR128_MITZ_32); + } + + /** + * Creates a {@code Builder} of a {@link BloomFilter BloomFilter}, with the expected number + * of insertions, and a default expected false positive probability of 3%. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements + * than specified, will result in its saturation, and a sharp deterioration of its + * false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided + * {@code Funnel} is. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed + * {@code BloomFilter}; must be positive + * @return a {@code BloomFilter} + */ + public static BloomFilter create(Funnel funnel, int expectedInsertions /* n */) { + return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions + } + + /* + * Cheat sheet: + * + * m: total bits + * n: expected insertions + * b: m/n, bits per insertion + + * p: expected false positive probability + * + * 1) Optimal k = b * ln2 + * 2) p = (1 - e ^ (-kn/m))^k + * 3) For optimal k: p = 2 ^ (-k) ~= 0.6185^b + * 4) For optimal k: m = -nlnp / ((ln2) ^ 2) + */ + + private static final double LN2 = Math.log(2); + private static final double LN2_SQUARED = LN2 * LN2; + + /** + * Computes the optimal k (number of hashes per element inserted in Bloom filter), given the + * expected insertions and total number of bits in the Bloom filter. + * + * See http://en.wikipedia.org/wiki/File:Bloom_filter_fp_probability.svg for the formula. + * + * @param n expected insertions (must be positive) + * @param m total number of bits in Bloom filter (must be positive) + */ + @VisibleForTesting static int optimalNumOfHashFunctions(int n, int m) { + return Math.max(1, (int) Math.round(m / n * LN2)); + } + + /** + * Computes m (total bits of Bloom filter) which is expected to achieve, for the specified + * expected insertions, the required false positive probability. + * + * See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for the formula. + * + * @param n expected insertions (must be positive) + * @param p false positive rate (must be 0 < p < 1) + */ + @VisibleForTesting static int optimalNumOfBits(int n, double p) { + return (int) (-n * Math.log(p) / LN2_SQUARED); + } + + private Object writeReplace() { + return new SerialForm(this); + } + + private static class SerialForm implements Serializable { + final long[] data; + final int numHashFunctions; + final Funnel funnel; + final Strategy strategy; + + SerialForm(BloomFilter bf) { + this.data = bf.bits.data; + this.numHashFunctions = bf.numHashFunctions; + this.funnel = bf.funnel; + this.strategy = bf.strategy; + } + Object readResolve() { + return new BloomFilter(new BitArray(data), numHashFunctions, funnel, strategy); + } + private static final long serialVersionUID = 1; + } +} diff --git a/guava/src/com/google/common/hash/BloomFilterStrategies.java b/guava/src/com/google/common/hash/BloomFilterStrategies.java new file mode 100644 index 0000000..9709e0b --- /dev/null +++ b/guava/src/com/google/common/hash/BloomFilterStrategies.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.math.IntMath; + +import java.math.RoundingMode; +import java.util.Arrays; + +/** + * Collections of strategies of generating the k * log(M) bits required for an element to + * be mapped to a BloomFilter of M bits and k hash functions. These + * strategies are part of the serialized form of the Bloom filters that use them, thus they must be + * preserved as is (no updates allowed, only introduction of new versions). + * + * Important: the order of the constants cannot change, and they cannot be deleted - we depend + * on their ordinal for BloomFilter serialization. + * + * @author Dimitris Andreou + */ +enum BloomFilterStrategies implements BloomFilter.Strategy { + /** + * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and + * Michael Mitzenmacher. The paper argues that this trick doesn't significantly deteriorate the + * performance of a Bloom filter (yet only needs two 32bit hash functions). + */ + MURMUR128_MITZ_32() { + @Override public boolean put(T object, Funnel funnel, + int numHashFunctions, BitArray bits) { + // TODO(user): when the murmur's shortcuts are implemented, update this code + long hash64 = Hashing.murmur3_128().newHasher().putObject(object, funnel).hash().asLong(); + int hash1 = (int) hash64; + int hash2 = (int) (hash64 >>> 32); + boolean bitsChanged = false; + for (int i = 1; i <= numHashFunctions; i++) { + int nextHash = hash1 + i * hash2; + if (nextHash < 0) { + nextHash = ~nextHash; + } + bitsChanged |= bits.set(nextHash % bits.size()); + } + return bitsChanged; + } + + @Override public boolean mightContain(T object, Funnel funnel, + int numHashFunctions, BitArray bits) { + long hash64 = Hashing.murmur3_128().newHasher().putObject(object, funnel).hash().asLong(); + int hash1 = (int) hash64; + int hash2 = (int) (hash64 >>> 32); + for (int i = 1; i <= numHashFunctions; i++) { + int nextHash = hash1 + i * hash2; + if (nextHash < 0) { + nextHash = ~nextHash; + } + if (!bits.get(nextHash % bits.size())) { + return false; + } + } + return true; + } + }; + + // Note: We use this instead of java.util.BitSet because we need access to the long[] data field + static class BitArray { + final long[] data; + int bitCount; + + BitArray(int bits) { + this(new long[IntMath.divide(bits, 64, RoundingMode.CEILING)]); + } + + // Used by serialization + BitArray(long[] data) { + checkArgument(data.length > 0, "data length is zero!"); + this.data = data; + int bitCount = 0; + for (long value : data) { + bitCount += Long.bitCount(value); + } + this.bitCount = bitCount; + } + + /** Returns true if the bit changed value. */ + boolean set(int index) { + if (!get(index)) { + data[index >> 6] |= (1L << index); + bitCount++; + return true; + } + return false; + } + + boolean get(int index) { + return (data[index >> 6] & (1L << index)) != 0; + } + + /** Number of bits */ + int size() { + return data.length * Long.SIZE; + } + + /** Number of set bits (1s) */ + int bitCount() { + return bitCount; + } + + BitArray copy() { + return new BitArray(data.clone()); + } + + @Override public boolean equals(Object o) { + if (o instanceof BitArray) { + BitArray bitArray = (BitArray) o; + return Arrays.equals(data, bitArray.data); + } + return false; + } + + @Override public int hashCode() { + return Arrays.hashCode(data); + } + } +} diff --git a/guava/src/com/google/common/hash/Funnel.java b/guava/src/com/google/common/hash/Funnel.java new file mode 100644 index 0000000..dbe0596 --- /dev/null +++ b/guava/src/com/google/common/hash/Funnel.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; + +import java.io.Serializable; + +/** + * An object which can send data from an object of type {@code T} into a {@code PrimitiveSink}. + * + * @author Dimitris Andreou + * @since 11.0 + */ +@Beta +public interface Funnel extends Serializable { + + /** + * Sends a stream of data from the {@code from} object into the sink {@code into}. There + * is no requirement that this data be complete enough to fully reconstitute the object + * later. + * + * @since 12.0 (in 11.0 version, {@code PrimitiveSink} was still called {@code Sink}) + */ + void funnel(T from, PrimitiveSink into); +} diff --git a/guava/src/com/google/common/hash/Funnels.java b/guava/src/com/google/common/hash/Funnels.java new file mode 100644 index 0000000..7f76202 --- /dev/null +++ b/guava/src/com/google/common/hash/Funnels.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.io.OutputStream; + +/** + * Funnels for common types. All implementations are serializable. + * + * @author Dimitris Andreou + * @since 11.0 + */ +@Beta +public final class Funnels { + private Funnels() {} + + /** + * Returns a funnel that extracts the bytes from a {@code byte} array. + */ + public static Funnel byteArrayFunnel() { + return ByteArrayFunnel.INSTANCE; + } + + private enum ByteArrayFunnel implements Funnel { + INSTANCE; + + public void funnel(byte[] from, PrimitiveSink into) { + into.putBytes(from); + } + + @Override public String toString() { + return "Funnels.byteArrayFunnel()"; + } + } + + /** + * Returns a funnel that extracts the characters from a {@code CharSequence}. + */ + public static Funnel stringFunnel() { + return StringFunnel.INSTANCE; + } + + private enum StringFunnel implements Funnel { + INSTANCE; + + public void funnel(CharSequence from, PrimitiveSink into) { + into.putString(from); + } + + @Override public String toString() { + return "Funnels.stringFunnel()"; + } + } + + /** + * Returns a funnel for integers. + * + * @since 13.0 + */ + public static Funnel integerFunnel() { + return IntegerFunnel.INSTANCE; + } + + private enum IntegerFunnel implements Funnel { + INSTANCE; + + public void funnel(Integer from, PrimitiveSink into) { + into.putInt(from); + } + + @Override public String toString() { + return "Funnels.integerFunnel()"; + } + } + + /** + * Returns a funnel for longs. + * + * @since 13.0 + */ + public static Funnel longFunnel() { + return LongFunnel.INSTANCE; + } + + private enum LongFunnel implements Funnel { + INSTANCE; + + public void funnel(Long from, PrimitiveSink into) { + into.putLong(from); + } + + @Override public String toString() { + return "Funnels.longFunnel()"; + } + } + + /** + * Wraps a {@code PrimitiveSink} as an {@link OutputStream}, so it is easy to + * {@link Funnel#funnel funnel} an object to a {@code PrimitiveSink} + * if there is already a way to write the contents of the object to an {@code OutputStream}. + * + *

The {@code close} and {@code flush} methods of the returned {@code OutputStream} + * do nothing, and no method throws {@code IOException}. + * + * @since 13.0 + */ + public static OutputStream asOutputStream(PrimitiveSink sink) { + return new SinkAsStream(sink); + } + + private static class SinkAsStream extends OutputStream { + final PrimitiveSink sink; + SinkAsStream(PrimitiveSink sink) { + this.sink = Preconditions.checkNotNull(sink); + } + + @Override public void write(int b) { + sink.putByte((byte) b); + } + + @Override public void write(byte[] bytes) { + sink.putBytes(bytes); + } + + @Override public void write(byte[] bytes, int off, int len) { + sink.putBytes(bytes, off, len); + } + + @Override public String toString() { + return "Funnels.asOutputStream(" + sink + ")"; + } + } +} diff --git a/guava/src/com/google/common/hash/HashCode.java b/guava/src/com/google/common/hash/HashCode.java new file mode 100644 index 0000000..ecd8706 --- /dev/null +++ b/guava/src/com/google/common/hash/HashCode.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Ints; + +import java.security.MessageDigest; + +/** + * An immutable hash code of arbitrary bit length. + * + * @author Dimitris Andreou + * @since 11.0 + */ +@Beta +public abstract class HashCode { + HashCode() {} + + /** + * Returns the first four bytes of {@linkplain #asBytes() this hashcode's bytes}, converted to + * an {@code int} value in little-endian order. + */ + public abstract int asInt(); + + /** + * Returns the first eight bytes of {@linkplain #asBytes() this hashcode's bytes}, converted to + * a {@code long} value in little-endian order. + * + * @throws IllegalStateException if {@code bits() < 64} + */ + public abstract long asLong(); + + /** + * Returns the value of this hash code as a byte array. The caller may modify the byte array; + * changes to it will not be reflected in this {@code HashCode} object or any other arrays + * returned by this method. + */ + // TODO(user): consider ByteString here, when that is available + public abstract byte[] asBytes(); + + /** + * Copies bytes from this hash code into {@code dest}. + * + * @param dest the byte array into which the hash code will be written + * @param offset the start offset in the data + * @param maxLength the maximum number of bytes to write + * @return the number of bytes written to {@code dest} + * @throws IndexOutOfBoundsException if there is not enough room in {@code dest} + */ + public int writeBytesTo(byte[] dest, int offset, int maxLength) { + byte[] hash = asBytes(); + maxLength = Ints.min(maxLength, hash.length); + Preconditions.checkPositionIndexes(offset, offset + maxLength, dest.length); + System.arraycopy(hash, 0, dest, offset, maxLength); + return maxLength; + } + + /** + * Returns the number of bits in this hash code; a positive multiple of 32. + */ + public abstract int bits(); + + @Override public boolean equals(Object object) { + if (object instanceof HashCode) { + HashCode that = (HashCode) object; + // Undocumented: this is a non-short-circuiting equals(), in case this is a cryptographic + // hash code, in which case we don't want to leak timing information + return MessageDigest.isEqual(this.asBytes(), that.asBytes()); + } + return false; + } + + /** + * Returns a "Java hash code" for this {@code HashCode} instance; this is well-defined + * (so, for example, you can safely put {@code HashCode} instances into a {@code + * HashSet}) but is otherwise probably not what you want to use. + */ + @Override public int hashCode() { + /* + * As long as the hash function that produced this isn't of horrible quality, this + * won't be of horrible quality either. + */ + return asInt(); + } + + /** + * Returns a string containing each byte of {@link #asBytes}, in order, as a two-digit unsigned + * hexadecimal number in lower case. + * + *

Note that if the output is considered to be a single hexadecimal number, this hash code's + * bytes are the big-endian representation of that number. This may be surprising since + * everything else in the hashing API uniformly treats multibyte values as little-endian. But + * this format conveniently matches that of utilities such as the UNIX {@code md5sum} command. + */ + @Override public String toString() { + byte[] bytes = asBytes(); + // TODO(user): Use c.g.common.base.ByteArrays once it is open sourced. + StringBuilder sb = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]); + } + return sb.toString(); + } + + private static final char[] hexDigits = "0123456789abcdef".toCharArray(); +} diff --git a/guava/src/com/google/common/hash/HashCodes.java b/guava/src/com/google/common/hash/HashCodes.java new file mode 100644 index 0000000..3aa3424 --- /dev/null +++ b/guava/src/com/google/common/hash/HashCodes.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; + +import java.io.Serializable; + +/** + * Static factories for creating {@link HashCode} instances; most users should never have to use + * this. All returned instances are {@link Serializable}. + * + * @author Dimitris Andreou + * @since 12.0 + */ +@Beta +public final class HashCodes { + private HashCodes() {} + + /** + * Creates a 32-bit {@code HashCode}, of which the bytes will form the passed int, interpreted + * in little endian order. + */ + public static HashCode fromInt(int hash) { + return new IntHashCode(hash); + } + + private static final class IntHashCode extends HashCode implements Serializable { + final int hash; + + IntHashCode(int hash) { + this.hash = hash; + } + + @Override public int bits() { + return 32; + } + + @Override public byte[] asBytes() { + return new byte[] { + (byte) hash, + (byte) (hash >> 8), + (byte) (hash >> 16), + (byte) (hash >> 24)}; + } + + @Override public int asInt() { + return hash; + } + + @Override public long asLong() { + throw new IllegalStateException("this HashCode only has 32 bits; cannot create a long"); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a 64-bit {@code HashCode}, of which the bytes will form the passed long, interpreted + * in little endian order. + */ + public static HashCode fromLong(long hash) { + return new LongHashCode(hash); + } + + private static final class LongHashCode extends HashCode implements Serializable { + final long hash; + + LongHashCode(long hash) { + this.hash = hash; + } + + @Override public int bits() { + return 64; + } + + @Override public byte[] asBytes() { + return new byte[] { + (byte) hash, + (byte) (hash >> 8), + (byte) (hash >> 16), + (byte) (hash >> 24), + (byte) (hash >> 32), + (byte) (hash >> 40), + (byte) (hash >> 48), + (byte) (hash >> 56)}; + } + + @Override public int asInt() { + return (int) hash; + } + + @Override public long asLong() { + return hash; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a {@code HashCode} from a byte array. The array is defensively copied to preserve + * the immutability contract of {@code HashCode}. The array must be at least of length 4. + */ + public static HashCode fromBytes(byte[] bytes) { + checkArgument(bytes.length >= 4, "A HashCode must contain at least 4 bytes."); + return fromBytesNoCopy(bytes.clone()); + } + + /** + * Creates a {@code HashCode} from a byte array. The array is not copied defensively, + * so it must be handed-off so as to preserve the immutability contract of {@code HashCode}. + * The array must be at least of length 4 (not checked). + */ + static HashCode fromBytesNoCopy(byte[] bytes) { + return new BytesHashCode(bytes); + } + + private static final class BytesHashCode extends HashCode implements Serializable { + final byte[] bytes; + + BytesHashCode(byte[] bytes) { + this.bytes = bytes; + } + + @Override public int bits() { + return bytes.length * 8; + } + + @Override public byte[] asBytes() { + return bytes.clone(); + } + + @Override public int asInt() { + return (bytes[0] & 0xFF) + | ((bytes[1] & 0xFF) << 8) + | ((bytes[2] & 0xFF) << 16) + | ((bytes[3] & 0xFF) << 24); + } + + @Override public long asLong() { + if (bytes.length < 8) { + // Checking this to throw the correct type of exception + throw new IllegalStateException("Not enough bytes"); + } + return (bytes[0] & 0xFFL) + | ((bytes[1] & 0xFFL) << 8) + | ((bytes[2] & 0xFFL) << 16) + | ((bytes[3] & 0xFFL) << 24) + | ((bytes[4] & 0xFFL) << 32) + | ((bytes[5] & 0xFFL) << 40) + | ((bytes[6] & 0xFFL) << 48) + | ((bytes[7] & 0xFFL) << 56); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/hash/HashFunction.java b/guava/src/com/google/common/hash/HashFunction.java new file mode 100644 index 0000000..328fd35 --- /dev/null +++ b/guava/src/com/google/common/hash/HashFunction.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import com.google.common.primitives.Ints; + +import java.nio.charset.Charset; + +/** + * A hash function is a collision-averse pure function that maps an arbitrary block of + * data to a number called a hash code. + * + *

Definition

+ * + *

Unpacking this definition: + * + *

    + *
  • block of data: the input for a hash function is always, in concept, an + * ordered byte array. This hashing API accepts an arbitrary sequence of byte and + * multibyte values (via {@link Hasher}), but this is merely a convenience; these are + * always translated into raw byte sequences under the covers. + * + *
  • hash code: each hash function always yields hash codes of the same fixed bit + * length (given by {@link #bits}). For example, {@link Hashing#sha1} produces a + * 160-bit number, while {@link Hashing#murmur3_32()} yields only 32 bits. Because a + * {@code long} value is clearly insufficient to hold all hash code values, this API + * represents a hash code as an instance of {@link HashCode}. + * + *
  • pure function: the value produced must depend only on the input bytes, in + * the order they appear. Input data is never modified. {@link HashFunction} instances + * should always be stateless, and therefore thread-safe. + * + *
  • collision-averse: while it can't be helped that a hash function will + * sometimes produce the same hash code for distinct inputs (a "collision"), every + * hash function strives to some degree to make this unlikely. (Without this + * condition, a function that always returns zero could be called a hash function. It + * is not.) + *
+ * + *

Summarizing the last two points: "equal yield equal always; unequal yield + * unequal often." This is the most important characteristic of all hash functions. + * + *

Desirable properties

+ * + *

A high-quality hash function strives for some subset of the following virtues: + * + *

    + *
  • collision-resistant: while the definition above requires making at least + * some token attempt, one measure of the quality of a hash function is how + * well it succeeds at this goal. Important note: it may be easy to achieve the + * theoretical minimum collision rate when using completely random sample + * input. The true test of a hash function is how it performs on representative + * real-world data, which tends to contain many hidden patterns and clumps. The goal + * of a good hash function is to stamp these patterns out as thoroughly as possible. + * + *
  • bit-dispersing: masking out any single bit from a hash code should + * yield only the expected twofold increase to all collision rates. Informally, + * the "information" in the hash code should be as evenly "spread out" through the + * hash code's bits as possible. The result is that, for example, when choosing a + * bucket in a hash table of size 2^8, any eight bits could be consistently + * used. + * + *
  • cryptographic: certain hash functions such as {@link Hashing#sha512} are + * designed to make it as infeasible as possible to reverse-engineer the input that + * produced a given hash code, or even to discover any two distinct inputs that + * yield the same result. These are called cryptographic hash functions. But, + * whenever it is learned that either of these feats has become computationally + * feasible, the function is deemed "broken" and should no longer be used for secure + * purposes. (This is the likely eventual fate of all cryptographic hashes.) + * + *
  • fast: perhaps self-explanatory, but often the most important consideration. + * We have published microbenchmark results for many + * common hash functions. + *
+ * + *

Providing input to a hash function

+ * + *

The primary way to provide the data that your hash function should act on is via a + * {@link Hasher}. Obtain a new hasher from the hash function using {@link #newHasher}, + * "push" the relevant data into it using methods like {@link Hasher#putBytes(byte[])}, + * and finally ask for the {@code HashCode} when finished using {@link Hasher#hash}. (See + * an {@linkplain #newHasher example} of this.) + * + *

If all you want to hash is a single byte array, string or {@code long} value, there + * are convenient shortcut methods defined directly on {@link HashFunction} to make this + * easier. + * + *

Hasher accepts primitive data types, but can also accept any Object of type {@code + * T} provided that you implement a {@link Funnel Funnel} to specify how to "feed" data + * from that object into the function. (See {@linkplain Hasher#putObject an example} of + * this.) + * + *

Compatibility note: Throughout this API, multibyte values are always + * interpreted in little-endian order. That is, hashing the byte array {@code + * {0x01, 0x02, 0x03, 0x04}} is equivalent to hashing the {@code int} value {@code + * 0x04030201}. If this isn't what you need, methods such as {@link Integer#reverseBytes} + * and {@link Ints#toByteArray} will help. + * + *

Relationship to {@link Object#hashCode}

+ * + *

Java's baked-in concept of hash codes is constrained to 32 bits, and provides no + * separation between hash algorithms and the data they act on, so alternate hash + * algorithms can't be easily substituted. Also, implementations of {@code hashCode} tend + * to be poor-quality, in part because they end up depending on other existing + * poor-quality {@code hashCode} implementations, including those in many JDK classes. + * + *

{@code Object.hashCode} implementations tend to be very fast, but have weak + * collision prevention and no expectation of bit dispersion. This leaves them + * perfectly suitable for use in hash tables, because extra collisions cause only a slight + * performance hit, while poor bit dispersion is easily corrected using a secondary hash + * function (which all reasonable hash table implementations in Java use). For the many + * uses of hash functions beyond data structures, however, {@code Object.hashCode} almost + * always falls short -- hence this library. + * + * @author Kevin Bourrillion + * @since 11.0 + */ +@Beta +public interface HashFunction { + /** + * Begins a new hash code computation by returning an initialized, stateful {@code + * Hasher} instance that is ready to receive data. Example:

   {@code
+   *
+   *   HashFunction hf = Hashing.md5();
+   *   HashCode hc = hf.newHasher()
+   *       .putLong(id)
+   *       .putString(name)
+   *       .hash();}
+ */ + Hasher newHasher(); + + /** + * Begins a new hash code computation as {@link #newHasher()}, but provides a hint of the + * expected size of the input (in bytes). This is only important for non-streaming hash + * functions (hash functions that need to buffer their whole input before processing any + * of it). + */ + Hasher newHasher(int expectedInputSize); + + /** + * Shortcut for {@code newHasher().putInt(input).hash()}; returns the hash code for the given + * {@code int} value, interpreted in little-endian byte order. The implementation might + * perform better than its longhand equivalent, but should not perform worse. + * + * @since 12.0 + */ + HashCode hashInt(int input); + + /** + * Shortcut for {@code newHasher().putLong(input).hash()}; returns the hash code for the + * given {@code long} value, interpreted in little-endian byte order. The implementation + * might perform better than its longhand equivalent, but should not perform worse. + */ + HashCode hashLong(long input); + + /** + * Shortcut for {@code newHasher().putBytes(input).hash()}. The implementation + * might perform better than its longhand equivalent, but should not perform + * worse. + */ + HashCode hashBytes(byte[] input); + + /** + * Shortcut for {@code newHasher().putBytes(input, off, len).hash()}. The implementation + * might perform better than its longhand equivalent, but should not perform + * worse. + * + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > bytes.length} + * or {@code len < 0} + */ + HashCode hashBytes(byte[] input, int off, int len); + + /** + * Shortcut for {@code newHasher().putString(input).hash()}. The implementation might + * perform better than its longhand equivalent, but should not perform worse. Note that no + * character encoding is performed; the low byte and high byte of each character are hashed + * directly (in that order). This is equivalent to using + * {@code hashString(input, Charsets.UTF_16LE)}. + */ + HashCode hashString(CharSequence input); + + /** + * Shortcut for {@code newHasher().putString(input, charset).hash()}. Characters are encoded + * using the given {@link Charset}. The implementation might perform better than its + * longhand equivalent, but should not perform worse. + */ + HashCode hashString(CharSequence input, Charset charset); + + /** + * Returns the number of bits (a multiple of 32) that each hash code produced by this + * hash function has. + */ + int bits(); +} diff --git a/guava/src/com/google/common/hash/Hasher.java b/guava/src/com/google/common/hash/Hasher.java new file mode 100644 index 0000000..ddb4ca6 --- /dev/null +++ b/guava/src/com/google/common/hash/Hasher.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; + +import java.nio.charset.Charset; + +/** + * A {@link PrimitiveSink} that can compute a hash code after reading the input. Each hasher should + * translate all multibyte values ({@link #putInt(int)}, {@link #putLong(long)}, etc) to bytes + * in little-endian order. + * + * @author Kevin Bourrillion + * @since 11.0 + */ +@Beta +public interface Hasher extends PrimitiveSink { + @Override Hasher putByte(byte b); + @Override Hasher putBytes(byte[] bytes); + @Override Hasher putBytes(byte[] bytes, int off, int len); + @Override Hasher putShort(short s); + @Override Hasher putInt(int i); + @Override Hasher putLong(long l); + + /** + * Equivalent to {@code putInt(Float.floatToRawIntBits(f))}. + */ + @Override Hasher putFloat(float f); + + /** + * Equivalent to {@code putLong(Double.doubleToRawLongBits(d))}. + */ + @Override Hasher putDouble(double d); + + /** + * Equivalent to {@code putByte(b ? (byte) 1 : (byte) 0)}. + */ + @Override Hasher putBoolean(boolean b); + @Override Hasher putChar(char c); + + /** + * Equivalent to processing each {@code char} value in the {@code CharSequence}, in order. + * The input must not be updated while this method is in progress. + */ + @Override Hasher putString(CharSequence charSequence); + + /** + * Equivalent to {@code putBytes(charSequence.toString().getBytes(charset))}. + */ + @Override Hasher putString(CharSequence charSequence, Charset charset); + + /** + * A simple convenience for {@code funnel.funnel(object, this)}. + */ + Hasher putObject(T instance, Funnel funnel); + + /** + * Computes a hash code based on the data that have been provided to this hasher. The result is + * unspecified if this method is called more than once on the same instance. + */ + HashCode hash(); +} diff --git a/guava/src/com/google/common/hash/Hashing.java b/guava/src/com/google/common/hash/Hashing.java new file mode 100644 index 0000000..b748bc3 --- /dev/null +++ b/guava/src/com/google/common/hash/Hashing.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.UnsignedInts; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.Iterator; + +/** + * Static methods to obtain {@link HashFunction} instances, and other static + * hashing-related utilities. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@Beta +public final class Hashing { + private Hashing() {} + + /** + * Used to randomize {@link #goodFastHash} instances, so that programs which persist anything + * dependent on hashcodes of those, will fail sooner than later. + */ + private static final int GOOD_FAST_HASH_SEED = (int) System.currentTimeMillis(); + + // Used by goodFastHash when minimumBits == 32. + private static final HashFunction GOOD_FAST_HASH_FUNCTION_32 = murmur3_32(GOOD_FAST_HASH_SEED); + + // Used by goodFastHash when 32 < minimumBits <= 128. + private static final HashFunction GOOD_FAST_HASH_FUNCTION_128 = murmur3_128(GOOD_FAST_HASH_SEED); + + /** + * Returns a general-purpose, non-cryptographic-strength, streaming hash function that + * produces hash codes of length at least {@code minimumBits}. Users without specific + * compatibility requirements and who do not persist the hash codes are encouraged to + * choose this hash function. + * + *

Repeated calls to {@link #goodFastHash} with the same {@code minimumBits} value will + * return {@link HashFunction} instances with identical behavior (but not necessarily the + * same instance) for the duration of the current virtual machine. + * + *

Warning: the implementation is unspecified and is subject to change. + * + * @throws IllegalArgumentException if {@code minimumBits} is not positive + */ + public static HashFunction goodFastHash(int minimumBits) { + int bits = checkPositiveAndMakeMultipleOf32(minimumBits); + + if (bits == 32) { + return GOOD_FAST_HASH_FUNCTION_32; + } + if (bits <= 128) { + return GOOD_FAST_HASH_FUNCTION_128; + } + + // Otherwise, join together some 128-bit murmur3s + int hashFunctionsNeeded = (bits + 127) / 128; + HashFunction[] hashFunctions = new HashFunction[hashFunctionsNeeded]; + hashFunctions[0] = GOOD_FAST_HASH_FUNCTION_128; + int seed = GOOD_FAST_HASH_SEED; + for (int i = 1; i < hashFunctionsNeeded; i++) { + seed += 1500450271; // a prime; shouldn't matter + hashFunctions[i] = murmur3_128(seed); + } + return new ConcatenatedHashFunction(hashFunctions); + } + + /** + * Returns a hash function implementing the + * 32-bit murmur3 + * algorithm (little-endian variant), using the given seed value. + */ + public static HashFunction murmur3_32(int seed) { + return new Murmur3_32HashFunction(seed); + } + + /** + * Returns a hash function implementing the + * 32-bit murmur3 + * algorithm (little-endian variant), using a seed value of zero. + */ + public static HashFunction murmur3_32() { + return MURMUR3_32; + } + + private static final Murmur3_32HashFunction MURMUR3_32 = new Murmur3_32HashFunction(0); + + /** + * Returns a hash function implementing the + * + * 128-bit murmur3 algorithm, x64 variant (little-endian variant), using the given seed + * value. + */ + public static HashFunction murmur3_128(int seed) { + return new Murmur3_128HashFunction(seed); + } + + /** + * Returns a hash function implementing the + * + * 128-bit murmur3 algorithm, x64 variant (little-endian variant), using a seed value + * of zero. + */ + public static HashFunction murmur3_128() { + return MURMUR3_128; + } + + private static final Murmur3_128HashFunction MURMUR3_128 = new Murmur3_128HashFunction(0); + + /** + * Returns a hash function implementing the MD5 hash algorithm (128 hash bits) by delegating to + * the MD5 {@link MessageDigest}. + */ + public static HashFunction md5() { + return MD5; + } + + private static final HashFunction MD5 = new MessageDigestHashFunction("MD5"); + + /** + * Returns a hash function implementing the SHA-1 algorithm (160 hash bits) by delegating to the + * SHA-1 {@link MessageDigest}. + */ + public static HashFunction sha1() { + return SHA_1; + } + + private static final HashFunction SHA_1 = new MessageDigestHashFunction("SHA-1"); + + /** + * Returns a hash function implementing the SHA-256 algorithm (256 hash bits) by delegating to + * the SHA-256 {@link MessageDigest}. + */ + public static HashFunction sha256() { + return SHA_256; + } + + private static final HashFunction SHA_256 = new MessageDigestHashFunction("SHA-256"); + + /** + * Returns a hash function implementing the SHA-512 algorithm (512 hash bits) by delegating to the + * SHA-512 {@link MessageDigest}. + */ + public static HashFunction sha512() { + return SHA_512; + } + + private static final HashFunction SHA_512 = new MessageDigestHashFunction("SHA-512"); + + // Lazy initiliazation holder class idiom. + + /** + * If {@code hashCode} has enough bits, returns {@code hashCode.asLong()}, otherwise + * returns a {@code long} value with {@code hashCode.asInt()} as the least-significant + * four bytes and {@code 0x00} as each of the most-significant four bytes. + */ + public static long padToLong(HashCode hashCode) { + return (hashCode.bits() < 64) ? UnsignedInts.toLong(hashCode.asInt()) : hashCode.asLong(); + } + + /** + * Assigns to {@code hashCode} a "bucket" in the range {@code [0, buckets)}, in a uniform + * manner that minimizes the need for remapping as {@code buckets} grows. That is, + * {@code consistentHash(h, n)} equals: + * + *

    + *
  • {@code n - 1}, with approximate probability {@code 1/n} + *
  • {@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n}) + *
+ * + *

See the wikipedia + * article on consistent hashing for more information. + *

+ * If you might want to have weights for the buckets in the future, take a look at + * {@code weightedConsistentHash}. + */ + public static int consistentHash(HashCode hashCode, int buckets) { + return consistentHash(padToLong(hashCode), buckets); + } + + /** + * Assigns to {@code input} a "bucket" in the range {@code [0, buckets)}, in a uniform + * manner that minimizes the need for remapping as {@code buckets} grows. That is, + * {@code consistentHash(h, n)} equals: + * + *

    + *
  • {@code n - 1}, with approximate probability {@code 1/n} + *
  • {@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n}) + *
+ * + *

See the wikipedia + * article on consistent hashing for more information. + *

+ * If you might want to have weights for the buckets in the future, take a look at + * {@code weightedConsistentHash}. + */ + public static int consistentHash(long input, int buckets) { + checkArgument(buckets > 0, "buckets must be positive: %s", buckets); + LinearCongruentialGenerator generator = new LinearCongruentialGenerator(input); + int candidate = 0; + int next; + + // Jump from bucket to bucket until we go out of range + while (true) { + next = (int) ((candidate + 1) / generator.nextDouble()); + if (next >= 0 && next < buckets) { + candidate = next; + } else { + return candidate; + } + } + } + + /** + * Returns a hash code, having the same bit length as each of the input hash codes, + * that combines the information of these hash codes in an ordered fashion. That + * is, whenever two equal hash codes are produced by two calls to this method, it + * is as likely as possible that each was computed from the same + * input hash codes in the same order. + * + * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes + * do not all have the same bit length + */ + public static HashCode combineOrdered(Iterable hashCodes) { + Iterator iterator = hashCodes.iterator(); + checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine."); + int bits = iterator.next().bits(); + byte[] resultBytes = new byte[bits / 8]; + for (HashCode hashCode : hashCodes) { + byte[] nextBytes = hashCode.asBytes(); + checkArgument(nextBytes.length == resultBytes.length, + "All hashcodes must have the same bit length."); + for (int i = 0; i < nextBytes.length; i++) { + resultBytes[i] = (byte) (resultBytes[i] * 37 ^ nextBytes[i]); + } + } + return HashCodes.fromBytesNoCopy(resultBytes); + } + + /** + * Returns a hash code, having the same bit length as each of the input hash codes, + * that combines the information of these hash codes in an unordered fashion. That + * is, whenever two equal hash codes are produced by two calls to this method, it + * is as likely as possible that each was computed from the same + * input hash codes in some order. + * + * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes + * do not all have the same bit length + */ + public static HashCode combineUnordered(Iterable hashCodes) { + Iterator iterator = hashCodes.iterator(); + checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine."); + byte[] resultBytes = new byte[iterator.next().bits() / 8]; + for (HashCode hashCode : hashCodes) { + byte[] nextBytes = hashCode.asBytes(); + checkArgument(nextBytes.length == resultBytes.length, + "All hashcodes must have the same bit length."); + for (int i = 0; i < nextBytes.length; i++) { + resultBytes[i] += nextBytes[i]; + } + } + return HashCodes.fromBytesNoCopy(resultBytes); + } + + /** + * Checks that the passed argument is positive, and ceils it to a multiple of 32. + */ + static int checkPositiveAndMakeMultipleOf32(int bits) { + checkArgument(bits > 0, "Number of bits must be positive"); + return (bits + 31) & ~31; + } + + // TODO(kevinb): Maybe expose this class via a static Hashing method? + @VisibleForTesting + static final class ConcatenatedHashFunction extends AbstractCompositeHashFunction { + private final int bits; + + ConcatenatedHashFunction(HashFunction... functions) { + super(functions); + int bitSum = 0; + for (HashFunction function : functions) { + bitSum += function.bits(); + } + this.bits = bitSum; + } + + @Override + HashCode makeHash(Hasher[] hashers) { + // TODO(user): Get rid of the ByteBuffer here? + byte[] bytes = new byte[bits / 8]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + for (Hasher hasher : hashers) { + buffer.put(hasher.hash().asBytes()); + } + return HashCodes.fromBytesNoCopy(bytes); + } + + @Override + public int bits() { + return bits; + } + } + + private static final class LinearCongruentialGenerator { + private long state; + + public LinearCongruentialGenerator(long seed) { + this.state = seed; + } + + public double nextDouble() { + state = 2862933555777941757L * state + 1; + return ((double) ((int) (state >>> 33) + 1)) / (0x1.0p31); + } + } +} diff --git a/guava/src/com/google/common/hash/MessageDigestHashFunction.java b/guava/src/com/google/common/hash/MessageDigestHashFunction.java new file mode 100644 index 0000000..9f52e9a --- /dev/null +++ b/guava/src/com/google/common/hash/MessageDigestHashFunction.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.primitives.Chars; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.common.primitives.Shorts; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * {@link HashFunction} adapter for {@link MessageDigest}s. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + */ +final class MessageDigestHashFunction extends AbstractStreamingHashFunction { + private final String algorithmName; + private final int bits; + + MessageDigestHashFunction(String algorithmName) { + this.algorithmName = algorithmName; + this.bits = getMessageDigest(algorithmName).getDigestLength() * 8; + } + + public int bits() { + return bits; + } + + private static MessageDigest getMessageDigest(String algorithmName) { + try { + return MessageDigest.getInstance(algorithmName); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + @Override public Hasher newHasher() { + return new MessageDigestHasher(getMessageDigest(algorithmName)); + } + + private static class MessageDigestHasher implements Hasher { + private final MessageDigest digest; + private final ByteBuffer scratch; // lazy convenience + private boolean done; + + private MessageDigestHasher(MessageDigest digest) { + this.digest = digest; + this.scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + } + + @Override public Hasher putByte(byte b) { + checkNotDone(); + digest.update(b); + return this; + } + + @Override public Hasher putBytes(byte[] bytes) { + checkNotDone(); + digest.update(bytes); + return this; + } + + @Override public Hasher putBytes(byte[] bytes, int off, int len) { + checkNotDone(); + checkPositionIndexes(off, off + len, bytes.length); + digest.update(bytes, off, len); + return this; + } + + @Override public Hasher putShort(short s) { + checkNotDone(); + scratch.putShort(s); + digest.update(scratch.array(), 0, Shorts.BYTES); + scratch.clear(); + return this; + } + + @Override public Hasher putInt(int i) { + checkNotDone(); + scratch.putInt(i); + digest.update(scratch.array(), 0, Ints.BYTES); + scratch.clear(); + return this; + } + + @Override public Hasher putLong(long l) { + checkNotDone(); + scratch.putLong(l); + digest.update(scratch.array(), 0, Longs.BYTES); + scratch.clear(); + return this; + } + + @Override public Hasher putFloat(float f) { + checkNotDone(); + scratch.putFloat(f); + digest.update(scratch.array(), 0, 4); + scratch.clear(); + return this; + } + + @Override public Hasher putDouble(double d) { + checkNotDone(); + scratch.putDouble(d); + digest.update(scratch.array(), 0, 8); + scratch.clear(); + return this; + } + + @Override public Hasher putBoolean(boolean b) { + return putByte(b ? (byte) 1 : (byte) 0); + } + + @Override public Hasher putChar(char c) { + checkNotDone(); + scratch.putChar(c); + digest.update(scratch.array(), 0, Chars.BYTES); + scratch.clear(); + return this; + } + + @Override public Hasher putString(CharSequence charSequence) { + for (int i = 0; i < charSequence.length(); i++) { + putChar(charSequence.charAt(i)); + } + return this; + } + + @Override public Hasher putString(CharSequence charSequence, Charset charset) { + return putBytes(charSequence.toString().getBytes(charset)); + } + + @Override public Hasher putObject(T instance, Funnel funnel) { + checkNotDone(); + funnel.funnel(instance, this); + return this; + } + + private void checkNotDone() { + checkState(!done, "Cannot use Hasher after calling #hash() on it"); + } + + public HashCode hash() { + done = true; + return HashCodes.fromBytesNoCopy(digest.digest()); + } + } +} diff --git a/guava/src/com/google/common/hash/Murmur3_128HashFunction.java b/guava/src/com/google/common/hash/Murmur3_128HashFunction.java new file mode 100644 index 0000000..c3ec8d8 --- /dev/null +++ b/guava/src/com/google/common/hash/Murmur3_128HashFunction.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.primitives.UnsignedBytes.toInt; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * See http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp + * MurmurHash3_x64_128 + * + * @author Austin Appleby + * @author Dimitris Andreou + */ +final class Murmur3_128HashFunction extends AbstractStreamingHashFunction implements Serializable { + // TODO(user): when the shortcuts are implemented, update BloomFilterStrategies + private final int seed; + + Murmur3_128HashFunction(int seed) { + this.seed = seed; + } + + @Override public int bits() { + return 128; + } + + @Override public Hasher newHasher() { + return new Murmur3_128Hasher(seed); + } + + private static final class Murmur3_128Hasher extends AbstractStreamingHasher { + private static final int CHUNK_SIZE = 16; + private static final long C1 = 0x87c37b91114253d5L; + private static final long C2 = 0x4cf5ad432745937fL; + private long h1; + private long h2; + private int length; + + Murmur3_128Hasher(int seed) { + super(CHUNK_SIZE); + this.h1 = seed; + this.h2 = seed; + this.length = 0; + } + + @Override protected void process(ByteBuffer bb) { + long k1 = bb.getLong(); + long k2 = bb.getLong(); + bmix64(k1, k2); + length += CHUNK_SIZE; + } + + private void bmix64(long k1, long k2) { + h1 ^= mixK1(k1); + + h1 = Long.rotateLeft(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + h2 ^= mixK2(k2); + + h2 = Long.rotateLeft(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + @Override protected void processRemaining(ByteBuffer bb) { + long k1 = 0; + long k2 = 0; + length += bb.remaining(); + switch (bb.remaining()) { + case 15: + k2 ^= (long) toInt(bb.get(14)) << 48; // fall through + case 14: + k2 ^= (long) toInt(bb.get(13)) << 40; // fall through + case 13: + k2 ^= (long) toInt(bb.get(12)) << 32; // fall through + case 12: + k2 ^= (long) toInt(bb.get(11)) << 24; // fall through + case 11: + k2 ^= (long) toInt(bb.get(10)) << 16; // fall through + case 10: + k2 ^= (long) toInt(bb.get(9)) << 8; // fall through + case 9: + k2 ^= (long) toInt(bb.get(8)); // fall through + case 8: + k1 ^= bb.getLong(); + break; + case 7: + k1 ^= (long) toInt(bb.get(6)) << 48; // fall through + case 6: + k1 ^= (long) toInt(bb.get(5)) << 40; // fall through + case 5: + k1 ^= (long) toInt(bb.get(4)) << 32; // fall through + case 4: + k1 ^= (long) toInt(bb.get(3)) << 24; // fall through + case 3: + k1 ^= (long) toInt(bb.get(2)) << 16; // fall through + case 2: + k1 ^= (long) toInt(bb.get(1)) << 8; // fall through + case 1: + k1 ^= (long) toInt(bb.get(0)); + break; + default: + throw new AssertionError("Should never get here."); + } + h1 ^= mixK1(k1); + h2 ^= mixK2(k2); + } + + @Override public HashCode makeHash() { + h1 ^= length; + h2 ^= length; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + return HashCodes.fromBytesNoCopy(ByteBuffer + .wrap(new byte[CHUNK_SIZE]) + .order(ByteOrder.LITTLE_ENDIAN) + .putLong(h1) + .putLong(h2) + .array()); + } + + private static long fmix64(long k) { + k ^= k >>> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >>> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >>> 33; + return k; + } + + private static long mixK1(long k1) { + k1 *= C1; + k1 = Long.rotateLeft(k1, 31); + k1 *= C2; + return k1; + } + + private static long mixK2(long k2) { + k2 *= C2; + k2 = Long.rotateLeft(k2, 33); + k2 *= C1; + return k2; + } + } + + private static final long serialVersionUID = 0L; +} diff --git a/guava/src/com/google/common/hash/Murmur3_32HashFunction.java b/guava/src/com/google/common/hash/Murmur3_32HashFunction.java new file mode 100644 index 0000000..0a1f850 --- /dev/null +++ b/guava/src/com/google/common/hash/Murmur3_32HashFunction.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.primitives.UnsignedBytes.toInt; + +import com.google.common.primitives.Chars; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +import java.io.Serializable; +import java.nio.ByteBuffer; + +/** + * See http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp + * MurmurHash3_x86_32 + * + * @author Austin Appleby + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + */ +final class Murmur3_32HashFunction extends AbstractStreamingHashFunction implements Serializable { + private static final int C1 = 0xcc9e2d51; + private static final int C2 = 0x1b873593; + + private final int seed; + + Murmur3_32HashFunction(int seed) { + this.seed = seed; + } + + @Override public int bits() { + return 32; + } + + @Override public Hasher newHasher() { + return new Murmur3_32Hasher(seed); + } + + @Override public HashCode hashInt(int input) { + int k1 = mixK1(input); + int h1 = mixH1(seed, k1); + + return fmix(h1, Ints.BYTES); + } + + @Override public HashCode hashLong(long input) { + int low = (int) input; + int high = (int) (input >>> 32); + + int k1 = mixK1(low); + int h1 = mixH1(seed, k1); + + k1 = mixK1(high); + h1 = mixH1(h1, k1); + + return fmix(h1, Longs.BYTES); + } + + // TODO(user): Maybe implement #hashBytes instead? + @Override public HashCode hashString(CharSequence input) { + int h1 = seed; + + // step through the CharSequence 2 chars at a time + for (int i = 1; i < input.length(); i += 2) { + int k1 = input.charAt(i - 1) | (input.charAt(i) << 16); + k1 = mixK1(k1); + h1 = mixH1(h1, k1); + } + + // deal with any remaining characters + if ((input.length() & 1) == 1) { + int k1 = input.charAt(input.length() - 1); + k1 = mixK1(k1); + h1 ^= k1; + } + + return fmix(h1, Chars.BYTES * input.length()); + } + + private static int mixK1(int k1) { + k1 *= C1; + k1 = Integer.rotateLeft(k1, 15); + k1 *= C2; + return k1; + } + + private static int mixH1(int h1, int k1) { + h1 ^= k1; + h1 = Integer.rotateLeft(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + return h1; + } + + // Finalization mix - force all bits of a hash block to avalanche + private static HashCode fmix(int h1, int length) { + h1 ^= length; + h1 ^= h1 >>> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >>> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >>> 16; + return HashCodes.fromInt(h1); + } + + private static final class Murmur3_32Hasher extends AbstractStreamingHasher { + private static final int CHUNK_SIZE = 4; + private int h1; + private int length; + + Murmur3_32Hasher(int seed) { + super(CHUNK_SIZE); + this.h1 = seed; + this.length = 0; + } + + @Override protected void process(ByteBuffer bb) { + int k1 = Murmur3_32HashFunction.mixK1(bb.getInt()); + h1 = Murmur3_32HashFunction.mixH1(h1, k1); + length += CHUNK_SIZE; + } + + @Override protected void processRemaining(ByteBuffer bb) { + length += bb.remaining(); + int k1 = 0; + switch (bb.remaining()) { + case 3: + k1 ^= toInt(bb.get(2)) << 16; // fall through + case 2: + k1 ^= toInt(bb.get(1)) << 8; // fall through + case 1: + k1 ^= toInt(bb.get(0)); + break; + default: + throw new AssertionError("Should never get here."); + } + h1 ^= Murmur3_32HashFunction.mixK1(k1); + } + + @Override public HashCode makeHash() { + return Murmur3_32HashFunction.fmix(h1, length); + } + } + + private static final long serialVersionUID = 0L; +} diff --git a/guava/src/com/google/common/hash/PrimitiveSink.java b/guava/src/com/google/common/hash/PrimitiveSink.java new file mode 100644 index 0000000..c851a1e --- /dev/null +++ b/guava/src/com/google/common/hash/PrimitiveSink.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; + +import java.nio.charset.Charset; + +/** + * An object which can receive a stream of primitive values. + * + * @author Kevin Bourrillion + * @since 12.0 (in 11.0 as {@code Sink}) + */ +@Beta +public interface PrimitiveSink { + /** + * Puts a byte into this sink. + * + * @param b a byte + * @return this instance + */ + PrimitiveSink putByte(byte b); + + /** + * Puts an array of bytes into this sink. + * + * @param bytes a byte array + * @return this instance + */ + PrimitiveSink putBytes(byte[] bytes); + + /** + * Puts a chunk of an array of bytes into this sink. {@code bytes[off]} is the first byte written, + * {@code bytes[off + len - 1]} is the last. + * + * @param bytes a byte array + * @param off the start offset in the array + * @param len the number of bytes to write + * @return this instance + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > bytes.length} or + * {@code len < 0} + */ + PrimitiveSink putBytes(byte[] bytes, int off, int len); + + /** + * Puts a short into this sink. + */ + PrimitiveSink putShort(short s); + + /** + * Puts an int into this sink. + */ + PrimitiveSink putInt(int i); + + /** + * Puts a long into this sink. + */ + PrimitiveSink putLong(long l); + + /** + * Puts a float into this sink. + */ + PrimitiveSink putFloat(float f); + + /** + * Puts a double into this sink. + */ + PrimitiveSink putDouble(double d); + + /** + * Puts a boolean into this sink. + */ + PrimitiveSink putBoolean(boolean b); + + /** + * Puts a character into this sink. + */ + PrimitiveSink putChar(char c); + + /** + * Puts a string into this sink. + */ + PrimitiveSink putString(CharSequence charSequence); + + /** + * Puts a string into this sink using the given charset. + */ + PrimitiveSink putString(CharSequence charSequence, Charset charset); +} diff --git a/guava/src/com/google/common/hash/package-info.java b/guava/src/com/google/common/hash/package-info.java new file mode 100644 index 0000000..7df4e73 --- /dev/null +++ b/guava/src/com/google/common/hash/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 The Guava Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +// TODO(user): when things stabilize, flesh this out +/** + * Hash functions and related structures. + * + *

See the Guava User Guide article on + * hashing. + */ +@ParametersAreNonnullByDefault +package com.google.common.hash; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/guava/src/com/google/common/io/AppendableWriter.java b/guava/src/com/google/common/io/AppendableWriter.java new file mode 100644 index 0000000..8033e46 --- /dev/null +++ b/guava/src/com/google/common/io/AppendableWriter.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.Writer; + +/** + * Writer that places all output on an {@link Appendable} target. If the target + * is {@link Flushable} or {@link Closeable}, flush()es and close()s will also + * be delegated to the target. + * + * @author Alan Green + * @author Sebastian Kanthak + * @since 1.0 + */ +class AppendableWriter extends Writer { + private final Appendable target; + private boolean closed; + + /** + * Creates a new writer that appends everything it writes to {@code target}. + * + * @param target target to which to append output + */ + AppendableWriter(Appendable target) { + this.target = target; + } + + /* + * Abstract methods from Writer + */ + + @Override public void write(char cbuf[], int off, int len) + throws IOException { + checkNotClosed(); + // It turns out that creating a new String is usually as fast, or faster + // than wrapping cbuf in a light-weight CharSequence. + target.append(new String(cbuf, off, len)); + } + + @Override public void flush() throws IOException { + checkNotClosed(); + if (target instanceof Flushable) { + ((Flushable) target).flush(); + } + } + + @Override public void close() throws IOException { + this.closed = true; + if (target instanceof Closeable) { + ((Closeable) target).close(); + } + } + + /* + * Override a few functions for performance reasons to avoid creating + * unnecessary strings. + */ + + @Override public void write(int c) throws IOException { + checkNotClosed(); + target.append((char) c); + } + + @Override public void write(String str) throws IOException { + checkNotClosed(); + target.append(str); + } + + @Override public void write(String str, int off, int len) throws IOException { + checkNotClosed(); + // tricky: append takes start, end pair... + target.append(str, off, off + len); + } + + @Override public Writer append(char c) throws IOException { + checkNotClosed(); + target.append(c); + return this; + } + + @Override public Writer append(CharSequence charSeq) throws IOException { + checkNotClosed(); + target.append(charSeq); + return this; + } + + @Override public Writer append(CharSequence charSeq, int start, int end) + throws IOException { + checkNotClosed(); + target.append(charSeq, start, end); + return this; + } + + private void checkNotClosed() throws IOException { + if (closed) { + throw new IOException("Cannot write to a closed writer."); + } + } +} diff --git a/guava/src/com/google/common/io/ByteArrayDataInput.java b/guava/src/com/google/common/io/ByteArrayDataInput.java new file mode 100644 index 0000000..3f4a467 --- /dev/null +++ b/guava/src/com/google/common/io/ByteArrayDataInput.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.DataInput; +import java.io.IOException; + +/** + * An extension of {@code DataInput} for reading from in-memory byte arrays; its + * methods offer identical functionality but do not throw {@link IOException}. + * + *

Warning: The caller is responsible for not attempting to read past + * the end of the array. If any method encounters the end of the array + * prematurely, it throws {@link IllegalStateException} to signify programmer + * error. This behavior is a technical violation of the supertype's + * contract, which specifies a checked exception. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public interface ByteArrayDataInput extends DataInput { + @Override void readFully(byte b[]); + + @Override void readFully(byte b[], int off, int len); + + @Override int skipBytes(int n); + + @Override boolean readBoolean(); + + @Override byte readByte(); + + @Override int readUnsignedByte(); + + @Override short readShort(); + + @Override int readUnsignedShort(); + + @Override char readChar(); + + @Override int readInt(); + + @Override long readLong(); + + @Override float readFloat(); + + @Override double readDouble(); + + @Override String readLine(); + + @Override String readUTF(); +} diff --git a/guava/src/com/google/common/io/ByteArrayDataOutput.java b/guava/src/com/google/common/io/ByteArrayDataOutput.java new file mode 100644 index 0000000..4d3dd97 --- /dev/null +++ b/guava/src/com/google/common/io/ByteArrayDataOutput.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.DataOutput; +import java.io.IOException; + +/** + * An extension of {@code DataOutput} for writing to in-memory byte arrays; its + * methods offer identical functionality but do not throw {@link IOException}. + * + * @author Jayaprabhakar Kadarkarai + * @since 1.0 + */ +public interface ByteArrayDataOutput extends DataOutput { + @Override void write(int b); + @Override void write(byte b[]); + @Override void write(byte b[], int off, int len); + @Override void writeBoolean(boolean v); + @Override void writeByte(int v); + @Override void writeShort(int v); + @Override void writeChar(int v); + @Override void writeInt(int v); + @Override void writeLong(long v); + @Override void writeFloat(float v); + @Override void writeDouble(double v); + @Override void writeChars(String s); + @Override void writeUTF(String s); + + /** + * @deprecated This method is dangerous as it discards the high byte of + * every character. For UTF-8, use {@code write(s.getBytes(Charsets.UTF_8))}. + */ + @Deprecated @Override void writeBytes(String s); + + /** + * Returns the contents that have been written to this instance, + * as a byte array. + */ + byte[] toByteArray(); +} diff --git a/guava/src/com/google/common/io/ByteProcessor.java b/guava/src/com/google/common/io/ByteProcessor.java new file mode 100644 index 0000000..71953be --- /dev/null +++ b/guava/src/com/google/common/io/ByteProcessor.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.IOException; + +/** + * A callback interface to process bytes from a stream. + * + *

{@link #processBytes} will be called for each line that is read, and + * should return {@code false} when you want to stop processing. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public interface ByteProcessor { + /** + * This method will be called for each chunk of bytes in an + * input stream. The implementation should process the bytes + * from {@code buf[off]} through {@code buf[off + len - 1]} + * (inclusive). + * + * @param buf the byte array containing the data to process + * @param off the initial offset into the array + * @param len the length of data to be processed + * @return true to continue processing, false to stop + */ + boolean processBytes(byte[] buf, int off, int len) throws IOException; + + /** Return the result of processing all the bytes. */ + T getResult(); +} diff --git a/guava/src/com/google/common/io/ByteStreams.java b/guava/src/com/google/common/io/ByteStreams.java new file mode 100644 index 0000000..388504e --- /dev/null +++ b/guava/src/com/google/common/io/ByteStreams.java @@ -0,0 +1,871 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.hash.Funnels; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.Arrays; +import java.util.zip.Checksum; + +/** + * Provides utility methods for working with byte arrays and I/O streams. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class ByteStreams { + private static final int BUF_SIZE = 0x1000; // 4K + + private ByteStreams() {} + + /** + * Returns a factory that will supply instances of + * {@link ByteArrayInputStream} that read from the given byte array. + * + * @param b the input buffer + * @return the factory + */ + public static InputSupplier newInputStreamSupplier( + byte[] b) { + return newInputStreamSupplier(b, 0, b.length); + } + + /** + * Returns a factory that will supply instances of + * {@link ByteArrayInputStream} that read from the given byte array. + * + * @param b the input buffer + * @param off the offset in the buffer of the first byte to read + * @param len the maximum number of bytes to read from the buffer + * @return the factory + */ + public static InputSupplier newInputStreamSupplier( + final byte[] b, final int off, final int len) { + return new InputSupplier() { + @Override + public ByteArrayInputStream getInput() { + return new ByteArrayInputStream(b, off, len); + } + }; + } + + /** + * Writes a byte array to an output stream from the given supplier. + * + * @param from the bytes to write + * @param to the output supplier + * @throws IOException if an I/O error occurs + */ + public static void write(byte[] from, + OutputSupplier to) throws IOException { + Preconditions.checkNotNull(from); + boolean threw = true; + OutputStream out = to.getOutput(); + try { + out.write(from); + threw = false; + } finally { + Closeables.close(out, threw); + } + } + + /** + * Opens input and output streams from the given suppliers, copies all + * bytes from the input to the output, and closes the streams. + * + * @param from the input factory + * @param to the output factory + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + public static long copy(InputSupplier from, + OutputSupplier to) throws IOException { + int successfulOps = 0; + InputStream in = from.getInput(); + try { + OutputStream out = to.getOutput(); + try { + long count = copy(in, out); + successfulOps++; + return count; + } finally { + Closeables.close(out, successfulOps < 1); + successfulOps++; + } + } finally { + Closeables.close(in, successfulOps < 2); + } + } + + /** + * Opens an input stream from the supplier, copies all bytes from the + * input to the output, and closes the input stream. Does not close + * or flush the output stream. + * + * @param from the input factory + * @param to the output stream to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + public static long copy(InputSupplier from, + OutputStream to) throws IOException { + boolean threw = true; + InputStream in = from.getInput(); + try { + long count = copy(in, to); + threw = false; + return count; + } finally { + Closeables.close(in, threw); + } + } + + /** + * Opens an output stream from the supplier, copies all bytes from the input + * to the output, and closes the output stream. Does not close or flush the + * input stream. + * + * @param from the input stream to read from + * @param to the output factory + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + * @since 10.0 + */ + public static long copy(InputStream from, + OutputSupplier to) throws IOException { + boolean threw = true; + OutputStream out = to.getOutput(); + try { + long count = copy(from, out); + threw = false; + return count; + } finally { + Closeables.close(out, threw); + } + } + + /** + * Copies all bytes from the input stream to the output stream. + * Does not close or flush either stream. + * + * @param from the input stream to read from + * @param to the output stream to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + public static long copy(InputStream from, OutputStream to) + throws IOException { + byte[] buf = new byte[BUF_SIZE]; + long total = 0; + while (true) { + int r = from.read(buf); + if (r == -1) { + break; + } + to.write(buf, 0, r); + total += r; + } + return total; + } + + /** + * Copies all bytes from the readable channel to the writable channel. + * Does not close or flush either channel. + * + * @param from the readable channel to read from + * @param to the writable channel to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + public static long copy(ReadableByteChannel from, + WritableByteChannel to) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE); + long total = 0; + while (from.read(buf) != -1) { + buf.flip(); + while (buf.hasRemaining()) { + total += to.write(buf); + } + buf.clear(); + } + return total; + } + + /** + * Reads all bytes from an input stream into a byte array. + * Does not close the stream. + * + * @param in the input stream to read from + * @return a byte array containing all the bytes from the stream + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copy(in, out); + return out.toByteArray(); + } + + /** + * Returns the data from a {@link InputStream} factory as a byte array. + * + * @param supplier the factory + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray( + InputSupplier supplier) throws IOException { + boolean threw = true; + InputStream in = supplier.getInput(); + try { + byte[] result = toByteArray(in); + threw = false; + return result; + } finally { + Closeables.close(in, threw); + } + } + + /** + * Returns a new {@link ByteArrayDataInput} instance to read from the {@code + * bytes} array from the beginning. + */ + public static ByteArrayDataInput newDataInput(byte[] bytes) { + return new ByteArrayDataInputStream(bytes); + } + + /** + * Returns a new {@link ByteArrayDataInput} instance to read from the {@code + * bytes} array, starting at the given position. + * + * @throws IndexOutOfBoundsException if {@code start} is negative or greater + * than the length of the array + */ + public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { + Preconditions.checkPositionIndex(start, bytes.length); + return new ByteArrayDataInputStream(bytes, start); + } + + private static class ByteArrayDataInputStream implements ByteArrayDataInput { + final DataInput input; + + ByteArrayDataInputStream(byte[] bytes) { + this.input = new DataInputStream(new ByteArrayInputStream(bytes)); + } + + ByteArrayDataInputStream(byte[] bytes, int start) { + this.input = new DataInputStream( + new ByteArrayInputStream(bytes, start, bytes.length - start)); + } + + @Override public void readFully(byte b[]) { + try { + input.readFully(b); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public void readFully(byte b[], int off, int len) { + try { + input.readFully(b, off, len); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public int skipBytes(int n) { + try { + return input.skipBytes(n); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public boolean readBoolean() { + try { + return input.readBoolean(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public byte readByte() { + try { + return input.readByte(); + } catch (EOFException e) { + throw new IllegalStateException(e); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public int readUnsignedByte() { + try { + return input.readUnsignedByte(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public short readShort() { + try { + return input.readShort(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public int readUnsignedShort() { + try { + return input.readUnsignedShort(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public char readChar() { + try { + return input.readChar(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public int readInt() { + try { + return input.readInt(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public long readLong() { + try { + return input.readLong(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public float readFloat() { + try { + return input.readFloat(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public double readDouble() { + try { + return input.readDouble(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public String readLine() { + try { + return input.readLine(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override public String readUTF() { + try { + return input.readUTF(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + + /** + * Returns a new {@link ByteArrayDataOutput} instance with a default size. + */ + public static ByteArrayDataOutput newDataOutput() { + return new ByteArrayDataOutputStream(); + } + + /** + * Returns a new {@link ByteArrayDataOutput} instance sized to hold + * {@code size} bytes before resizing. + * + * @throws IllegalArgumentException if {@code size} is negative + */ + public static ByteArrayDataOutput newDataOutput(int size) { + Preconditions.checkArgument(size >= 0, "Invalid size: %s", size); + return new ByteArrayDataOutputStream(size); + } + + @SuppressWarnings("deprecation") // for writeBytes + private static class ByteArrayDataOutputStream + implements ByteArrayDataOutput { + + final DataOutput output; + final ByteArrayOutputStream byteArrayOutputSteam; + + ByteArrayDataOutputStream() { + this(new ByteArrayOutputStream()); + } + + ByteArrayDataOutputStream(int size) { + this(new ByteArrayOutputStream(size)); + } + + ByteArrayDataOutputStream(ByteArrayOutputStream byteArrayOutputSteam) { + this.byteArrayOutputSteam = byteArrayOutputSteam; + output = new DataOutputStream(byteArrayOutputSteam); + } + + @Override public void write(int b) { + try { + output.write(b); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void write(byte[] b) { + try { + output.write(b); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void write(byte[] b, int off, int len) { + try { + output.write(b, off, len); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeBoolean(boolean v) { + try { + output.writeBoolean(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeByte(int v) { + try { + output.writeByte(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeBytes(String s) { + try { + output.writeBytes(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeChar(int v) { + try { + output.writeChar(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeChars(String s) { + try { + output.writeChars(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeDouble(double v) { + try { + output.writeDouble(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeFloat(float v) { + try { + output.writeFloat(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeInt(int v) { + try { + output.writeInt(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeLong(long v) { + try { + output.writeLong(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeShort(int v) { + try { + output.writeShort(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public void writeUTF(String s) { + try { + output.writeUTF(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override public byte[] toByteArray() { + return byteArrayOutputSteam.toByteArray(); + } + + } + + // TODO(chrisn): Not all streams support skipping. + /** Returns the length of a supplied input stream, in bytes. */ + public static long length(InputSupplier supplier) + throws IOException { + long count = 0; + boolean threw = true; + InputStream in = supplier.getInput(); + try { + while (true) { + // We skip only Integer.MAX_VALUE due to JDK overflow bugs. + long amt = in.skip(Integer.MAX_VALUE); + if (amt == 0) { + if (in.read() == -1) { + threw = false; + return count; + } + count++; + } else { + count += amt; + } + } + } finally { + Closeables.close(in, threw); + } + } + + /** + * Returns true if the supplied input streams contain the same bytes. + * + * @throws IOException if an I/O error occurs + */ + public static boolean equal(InputSupplier supplier1, + InputSupplier supplier2) throws IOException { + byte[] buf1 = new byte[BUF_SIZE]; + byte[] buf2 = new byte[BUF_SIZE]; + + boolean threw = true; + InputStream in1 = supplier1.getInput(); + try { + InputStream in2 = supplier2.getInput(); + try { + while (true) { + int read1 = read(in1, buf1, 0, BUF_SIZE); + int read2 = read(in2, buf2, 0, BUF_SIZE); + if (read1 != read2 || !Arrays.equals(buf1, buf2)) { + threw = false; + return false; + } else if (read1 != BUF_SIZE) { + threw = false; + return true; + } + } + } finally { + Closeables.close(in2, threw); + } + } finally { + Closeables.close(in1, threw); + } + } + + /** + * Attempts to read enough bytes from the stream to fill the given byte array, + * with the same behavior as {@link DataInput#readFully(byte[])}. + * Does not close the stream. + * + * @param in the input stream to read from. + * @param b the buffer into which the data is read. + * @throws EOFException if this stream reaches the end before reading all + * the bytes. + * @throws IOException if an I/O error occurs. + */ + public static void readFully(InputStream in, byte[] b) throws IOException { + readFully(in, b, 0, b.length); + } + + /** + * Attempts to read {@code len} bytes from the stream into the given array + * starting at {@code off}, with the same behavior as + * {@link DataInput#readFully(byte[], int, int)}. Does not close the + * stream. + * + * @param in the input stream to read from. + * @param b the buffer into which the data is read. + * @param off an int specifying the offset into the data. + * @param len an int specifying the number of bytes to read. + * @throws EOFException if this stream reaches the end before reading all + * the bytes. + * @throws IOException if an I/O error occurs. + */ + public static void readFully(InputStream in, byte[] b, int off, int len) + throws IOException { + if (read(in, b, off, len) != len) { + throw new EOFException(); + } + } + + /** + * Discards {@code n} bytes of data from the input stream. This method + * will block until the full amount has been skipped. Does not close the + * stream. + * + * @param in the input stream to read from + * @param n the number of bytes to skip + * @throws EOFException if this stream reaches the end before skipping all + * the bytes + * @throws IOException if an I/O error occurs, or the stream does not + * support skipping + */ + public static void skipFully(InputStream in, long n) throws IOException { + while (n > 0) { + long amt = in.skip(n); + if (amt == 0) { + // Force a blocking read to avoid infinite loop + if (in.read() == -1) { + throw new EOFException(); + } + n--; + } else { + n -= amt; + } + } + } + + /** + * Process the bytes of a supplied stream + * + * @param supplier the input stream factory + * @param processor the object to which to pass the bytes of the stream + * @return the result of the byte processor + * @throws IOException if an I/O error occurs + */ + public static T readBytes(InputSupplier supplier, + ByteProcessor processor) throws IOException { + byte[] buf = new byte[BUF_SIZE]; + boolean threw = true; + InputStream in = supplier.getInput(); + try { + int amt; + do { + amt = in.read(buf); + if (amt == -1) { + threw = false; + break; + } + } while (processor.processBytes(buf, 0, amt)); + return processor.getResult(); + } finally { + Closeables.close(in, threw); + } + } + + /** + * Computes and returns the checksum value for a supplied input stream. + * The checksum object is reset when this method returns successfully. + * + * @param supplier the input stream factory + * @param checksum the checksum object + * @return the result of {@link Checksum#getValue} after updating the + * checksum object with all of the bytes in the stream + * @throws IOException if an I/O error occurs + */ + public static long getChecksum(InputSupplier supplier, + final Checksum checksum) throws IOException { + return readBytes(supplier, new ByteProcessor() { + @Override + public boolean processBytes(byte[] buf, int off, int len) { + checksum.update(buf, off, len); + return true; + } + + @Override + public Long getResult() { + long result = checksum.getValue(); + checksum.reset(); + return result; + } + }); + } + + /** + * Computes the hash code of the data supplied by {@code supplier} using {@code + * hashFunction}. + * + * @param supplier the input stream factory + * @param hashFunction the hash function to use to hash the data + * @return the {@link HashCode} of all of the bytes in the input stream + * @throws IOException if an I/O error occurs + * @since 12.0 + */ + public static HashCode hash( + InputSupplier supplier, HashFunction hashFunction) + throws IOException { + Hasher hasher = hashFunction.newHasher(); + copy(supplier, Funnels.asOutputStream(hasher)); + return hasher.hash(); + } + + /** + * Reads some bytes from an input stream and stores them into the buffer array + * {@code b}. This method blocks until {@code len} bytes of input data have + * been read into the array, or end of file is detected. The number of bytes + * read is returned, possibly zero. Does not close the stream. + * + *

A caller can detect EOF if the number of bytes read is less than + * {@code len}. All subsequent calls on the same stream will return zero. + * + *

If {@code b} is null, a {@code NullPointerException} is thrown. If + * {@code off} is negative, or {@code len} is negative, or {@code off+len} is + * greater than the length of the array {@code b}, then an + * {@code IndexOutOfBoundsException} is thrown. If {@code len} is zero, then + * no bytes are read. Otherwise, the first byte read is stored into element + * {@code b[off]}, the next one into {@code b[off+1]}, and so on. The number + * of bytes read is, at most, equal to {@code len}. + * + * @param in the input stream to read from + * @param b the buffer into which the data is read + * @param off an int specifying the offset into the data + * @param len an int specifying the number of bytes to read + * @return the number of bytes read + * @throws IOException if an I/O error occurs + */ + public static int read(InputStream in, byte[] b, int off, int len) + throws IOException { + if (len < 0) { + throw new IndexOutOfBoundsException("len is negative"); + } + int total = 0; + while (total < len) { + int result = in.read(b, off + total, len - total); + if (result == -1) { + break; + } + total += result; + } + return total; + } + + /** + * Returns an {@link InputSupplier} that returns input streams from the + * an underlying supplier, where each stream starts at the given + * offset and is limited to the specified number of bytes. + * + * @param supplier the supplier from which to get the raw streams + * @param offset the offset in bytes into the underlying stream where + * the returned streams will start + * @param length the maximum length of the returned streams + * @throws IllegalArgumentException if offset or length are negative + */ + public static InputSupplier slice( + final InputSupplier supplier, + final long offset, + final long length) { + Preconditions.checkNotNull(supplier); + Preconditions.checkArgument(offset >= 0, "offset is negative"); + Preconditions.checkArgument(length >= 0, "length is negative"); + return new InputSupplier() { + @Override public InputStream getInput() throws IOException { + InputStream in = supplier.getInput(); + if (offset > 0) { + try { + skipFully(in, offset); + } catch (IOException e) { + Closeables.closeQuietly(in); + throw e; + } + } + return new LimitInputStream(in, length); + } + }; + } + + /** + * Joins multiple {@link InputStream} suppliers into a single supplier. + * Streams returned from the supplier will contain the concatenated data from + * the streams of the underlying suppliers. + * + *

Only one underlying input stream will be open at a time. Closing the + * joined stream will close the open underlying stream. + * + *

Reading from the joined stream will throw a {@link NullPointerException} + * if any of the suppliers are null or return null. + * + * @param suppliers the suppliers to concatenate + * @return a supplier that will return a stream containing the concatenated + * stream data + */ + public static InputSupplier join(final + Iterable> suppliers) { + return new InputSupplier() { + @Override public InputStream getInput() throws IOException { + return new MultiInputStream(suppliers.iterator()); + } + }; + } + + /** Varargs form of {@link #join(Iterable)}. */ + public static InputSupplier join( + InputSupplier... suppliers) { + return join(Arrays.asList(suppliers)); + } +} diff --git a/guava/src/com/google/common/io/CharStreams.java b/guava/src/com/google/common/io/CharStreams.java new file mode 100644 index 0000000..6a70375 --- /dev/null +++ b/guava/src/com/google/common/io/CharStreams.java @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Provides utility methods for working with character streams. + * + *

All method parameters must be non-null unless documented otherwise. + * + *

Some of the methods in this class take arguments with a generic type of + * {@code Readable & Closeable}. A {@link java.io.Reader} implements both of + * those interfaces. Similarly for {@code Appendable & Closeable} and + * {@link java.io.Writer}. + * + * @author Chris Nokleberg + * @author Bin Zhu + * @since 1.0 + */ +@Beta +public final class CharStreams { + private static final int BUF_SIZE = 0x800; // 2K chars (4K bytes) + + private CharStreams() {} + + /** + * Returns a factory that will supply instances of {@link StringReader} that + * read a string value. + * + * @param value the string to read + * @return the factory + */ + public static InputSupplier newReaderSupplier( + final String value) { + Preconditions.checkNotNull(value); + return new InputSupplier() { + @Override + public StringReader getInput() { + return new StringReader(value); + } + }; + } + + /** + * Returns a factory that will supply instances of {@link InputStreamReader}, + * using the given {@link InputStream} factory and character set. + * + * @param in the factory that will be used to open input streams + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return the factory + */ + public static InputSupplier newReaderSupplier( + final InputSupplier in, final Charset charset) { + Preconditions.checkNotNull(in); + Preconditions.checkNotNull(charset); + return new InputSupplier() { + @Override + public InputStreamReader getInput() throws IOException { + return new InputStreamReader(in.getInput(), charset); + } + }; + } + + /** + * Returns a factory that will supply instances of {@link OutputStreamWriter}, + * using the given {@link OutputStream} factory and character set. + * + * @param out the factory that will be used to open output streams + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @return the factory + */ + public static OutputSupplier newWriterSupplier( + final OutputSupplier out, final Charset charset) { + Preconditions.checkNotNull(out); + Preconditions.checkNotNull(charset); + return new OutputSupplier() { + @Override + public OutputStreamWriter getOutput() throws IOException { + return new OutputStreamWriter(out.getOutput(), charset); + } + }; + } + + /** + * Writes a character sequence (such as a string) to an appendable + * object from the given supplier. + * + * @param from the character sequence to write + * @param to the output supplier + * @throws IOException if an I/O error occurs + */ + public static void write(CharSequence from, + OutputSupplier to) throws IOException { + Preconditions.checkNotNull(from); + boolean threw = true; + W out = to.getOutput(); + try { + out.append(from); + threw = false; + } finally { + Closeables.close(out, threw); + } + } + + /** + * Opens {@link Readable} and {@link Appendable} objects from the + * given factories, copies all characters between the two, and closes + * them. + * + * @param from the input factory + * @param to the output factory + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + public static long copy(InputSupplier from, + OutputSupplier to) throws IOException { + int successfulOps = 0; + R in = from.getInput(); + try { + W out = to.getOutput(); + try { + long count = copy(in, out); + successfulOps++; + return count; + } finally { + Closeables.close(out, successfulOps < 1); + successfulOps++; + } + } finally { + Closeables.close(in, successfulOps < 2); + } + } + + /** + * Opens a {@link Readable} object from the supplier, copies all characters + * to the {@link Appendable} object, and closes the input. Does not close + * or flush the output. + * + * @param from the input factory + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + public static long copy( + InputSupplier from, Appendable to) throws IOException { + boolean threw = true; + R in = from.getInput(); + try { + long count = copy(in, to); + threw = false; + return count; + } finally { + Closeables.close(in, threw); + } + } + + /** + * Copies all characters between the {@link Readable} and {@link Appendable} + * objects. Does not close or flush either object. + * + * @param from the object to read from + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + public static long copy(Readable from, Appendable to) throws IOException { + CharBuffer buf = CharBuffer.allocate(BUF_SIZE); + long total = 0; + while (from.read(buf) != -1) { + buf.flip(); + to.append(buf); + total += buf.remaining(); + buf.clear(); + } + return total; + } + + /** + * Reads all characters from a {@link Readable} object into a {@link String}. + * Does not close the {@code Readable}. + * + * @param r the object to read from + * @return a string containing all the characters + * @throws IOException if an I/O error occurs + */ + public static String toString(Readable r) throws IOException { + return toStringBuilder(r).toString(); + } + + /** + * Returns the characters from a {@link Readable} & {@link Closeable} object + * supplied by a factory as a {@link String}. + * + * @param supplier the factory to read from + * @return a string containing all the characters + * @throws IOException if an I/O error occurs + */ + public static String toString( + InputSupplier supplier) throws IOException { + return toStringBuilder(supplier).toString(); + } + + /** + * Reads all characters from a {@link Readable} object into a new + * {@link StringBuilder} instance. Does not close the {@code Readable}. + * + * @param r the object to read from + * @return a {@link StringBuilder} containing all the characters + * @throws IOException if an I/O error occurs + */ + private static StringBuilder toStringBuilder(Readable r) throws IOException { + StringBuilder sb = new StringBuilder(); + copy(r, sb); + return sb; + } + + /** + * Returns the characters from a {@link Readable} & {@link Closeable} object + * supplied by a factory as a new {@link StringBuilder} instance. + * + * @param supplier the factory to read from + * @throws IOException if an I/O error occurs + */ + private static StringBuilder toStringBuilder( + InputSupplier supplier) throws IOException { + boolean threw = true; + R r = supplier.getInput(); + try { + StringBuilder result = toStringBuilder(r); + threw = false; + return result; + } finally { + Closeables.close(r, threw); + } + } + + /** + * Reads the first line from a {@link Readable} & {@link Closeable} object + * supplied by a factory. The line does not include line-termination + * characters, but does include other leading and trailing whitespace. + * + * @param supplier the factory to read from + * @return the first line, or null if the reader is empty + * @throws IOException if an I/O error occurs + */ + public static String readFirstLine( + InputSupplier supplier) throws IOException { + boolean threw = true; + R r = supplier.getInput(); + try { + String line = new LineReader(r).readLine(); + threw = false; + return line; + } finally { + Closeables.close(r, threw); + } + } + + /** + * Reads all of the lines from a {@link Readable} & {@link Closeable} object + * supplied by a factory. The lines do not include line-termination + * characters, but do include other leading and trailing whitespace. + * + * @param supplier the factory to read from + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines( + InputSupplier supplier) throws IOException { + boolean threw = true; + R r = supplier.getInput(); + try { + List result = readLines(r); + threw = false; + return result; + } finally { + Closeables.close(r, threw); + } + } + + /** + * Reads all of the lines from a {@link Readable} object. The lines do + * not include line-termination characters, but do include other + * leading and trailing whitespace. + * + *

Does not close the {@code Readable}. If reading files or resources you + * should use the {@link Files#readLines} and {@link Resources#readLines} + * methods. + * + * @param r the object to read from + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines(Readable r) throws IOException { + List result = new ArrayList(); + LineReader lineReader = new LineReader(r); + String line; + while ((line = lineReader.readLine()) != null) { + result.add(line); + } + return result; + } + + /** + * Streams lines from a {@link Readable} and {@link Closeable} object + * supplied by a factory, stopping when our callback returns false, or we + * have read all of the lines. + * + * @param supplier the factory to read from + * @param callback the LineProcessor to use to handle the lines + * @return the output of processing the lines + * @throws IOException if an I/O error occurs + */ + public static T readLines( + InputSupplier supplier, LineProcessor callback) throws IOException { + boolean threw = true; + R r = supplier.getInput(); + try { + LineReader lineReader = new LineReader(r); + String line; + while ((line = lineReader.readLine()) != null) { + if (!callback.processLine(line)) { + break; + } + } + threw = false; + } finally { + Closeables.close(r, threw); + } + return callback.getResult(); + } + + /** + * Joins multiple {@link Reader} suppliers into a single supplier. + * Reader returned from the supplier will contain the concatenated data + * from the readers of the underlying suppliers. + * + *

Reading from the joined reader will throw a {@link NullPointerException} + * if any of the suppliers are null or return null. + * + *

Only one underlying reader will be open at a time. Closing the + * joined reader will close the open underlying reader. + * + * @param suppliers the suppliers to concatenate + * @return a supplier that will return a reader containing the concatenated + * data + */ + public static InputSupplier join( + final Iterable> suppliers) { + return new InputSupplier() { + @Override public Reader getInput() throws IOException { + return new MultiReader(suppliers.iterator()); + } + }; + } + + /** Varargs form of {@link #join(Iterable)}. */ + public static InputSupplier join( + InputSupplier... suppliers) { + return join(Arrays.asList(suppliers)); + } + + /** + * Discards {@code n} characters of data from the reader. This method + * will block until the full amount has been skipped. Does not close the + * reader. + * + * @param reader the reader to read from + * @param n the number of characters to skip + * @throws EOFException if this stream reaches the end before skipping all + * the bytes + * @throws IOException if an I/O error occurs + */ + public static void skipFully(Reader reader, long n) throws IOException { + while (n > 0) { + long amt = reader.skip(n); + if (amt == 0) { + // force a blocking read + if (reader.read() == -1) { + throw new EOFException(); + } + n--; + } else { + n -= amt; + } + } + } + + /** + * Returns a Writer that sends all output to the given {@link Appendable} + * target. Closing the writer will close the target if it is {@link + * Closeable}, and flushing the writer will flush the target if it is {@link + * java.io.Flushable}. + * + * @param target the object to which output will be sent + * @return a new Writer object, unless target is a Writer, in which case the + * target is returned + */ + public static Writer asWriter(Appendable target) { + if (target instanceof Writer) { + return (Writer) target; + } + return new AppendableWriter(target); + } +} diff --git a/guava/src/com/google/common/io/Closeables.java b/guava/src/com/google/common/io/Closeables.java new file mode 100644 index 0000000..e619887 --- /dev/null +++ b/guava/src/com/google/common/io/Closeables.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; + +import java.io.Closeable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +/** + * Utility methods for working with {@link Closeable} objects. + * + * @author Michael Lancaster + * @since 1.0 + */ +@Beta +public final class Closeables { + @VisibleForTesting static final Logger logger + = Logger.getLogger(Closeables.class.getName()); + + private Closeables() {} + + /** + * Closes a {@link Closeable}, with control over whether an + * {@code IOException} may be thrown. This is primarily useful in a + * finally block, where a thrown exception needs to be logged but not + * propagated (otherwise the original exception will be lost). + * + *

If {@code swallowIOException} is true then we never throw + * {@code IOException} but merely log it. + * + *

Example: + * + *

public void useStreamNicely() throws IOException {
+   * SomeStream stream = new SomeStream("foo");
+   * boolean threw = true;
+   * try {
+   *   // Some code which does something with the Stream. May throw a
+   *   // Throwable.
+   *   threw = false; // No throwable thrown.
+   * } finally {
+   *   // Close the stream.
+   *   // If an exception occurs, only rethrow it if (threw==false).
+   *   Closeables.close(stream, threw);
+   * }
+   * 
+ * + * @param closeable the {@code Closeable} object to be closed, or null, + * in which case this method does nothing + * @param swallowIOException if true, don't propagate IO exceptions + * thrown by the {@code close} methods + * @throws IOException if {@code swallowIOException} is false and + * {@code close} throws an {@code IOException}. + */ + public static void close(@Nullable Closeable closeable, + boolean swallowIOException) throws IOException { + if (closeable == null) { + return; + } + try { + closeable.close(); + } catch (IOException e) { + if (swallowIOException) { + logger.log(Level.WARNING, + "IOException thrown while closing Closeable.", e); + } else { + throw e; + } + } + } + + /** + * Equivalent to calling {@code close(closeable, true)}, but with no + * IOException in the signature. + * @param closeable the {@code Closeable} object to be closed, or null, in + * which case this method does nothing + */ + public static void closeQuietly(@Nullable Closeable closeable) { + try { + close(closeable, true); + } catch (IOException e) { + logger.log(Level.SEVERE, "IOException should not have been thrown.", e); + } + } +} diff --git a/guava/src/com/google/common/io/CountingInputStream.java b/guava/src/com/google/common/io/CountingInputStream.java new file mode 100644 index 0000000..d11c8ec --- /dev/null +++ b/guava/src/com/google/common/io/CountingInputStream.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@link InputStream} that counts the number of bytes read. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class CountingInputStream extends FilterInputStream { + + private long count; + private long mark = -1; + + /** + * Wraps another input stream, counting the number of bytes read. + * + * @param in the input stream to be wrapped + */ + public CountingInputStream(InputStream in) { + super(in); + } + + /** Returns the number of bytes read. */ + public long getCount() { + return count; + } + + @Override public int read() throws IOException { + int result = in.read(); + if (result != -1) { + count++; + } + return result; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + int result = in.read(b, off, len); + if (result != -1) { + count += result; + } + return result; + } + + @Override public long skip(long n) throws IOException { + long result = in.skip(n); + count += result; + return result; + } + + @Override public synchronized void mark(int readlimit) { + in.mark(readlimit); + mark = count; + // it's okay to mark even if mark isn't supported, as reset won't work + } + + @Override public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + count = mark; + } +} diff --git a/guava/src/com/google/common/io/CountingOutputStream.java b/guava/src/com/google/common/io/CountingOutputStream.java new file mode 100644 index 0000000..5f57714 --- /dev/null +++ b/guava/src/com/google/common/io/CountingOutputStream.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An OutputStream that counts the number of bytes written. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class CountingOutputStream extends FilterOutputStream { + + private long count; + + /** + * Wraps another output stream, counting the number of bytes written. + * + * @param out the output stream to be wrapped + */ + public CountingOutputStream(OutputStream out) { + super(out); + } + + /** Returns the number of bytes written. */ + public long getCount() { + return count; + } + + @Override public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + count += len; + } + + @Override public void write(int b) throws IOException { + out.write(b); + count++; + } +} diff --git a/guava/src/com/google/common/io/FileBackedOutputStream.java b/guava/src/com/google/common/io/FileBackedOutputStream.java new file mode 100644 index 0000000..ce593b2 --- /dev/null +++ b/guava/src/com/google/common/io/FileBackedOutputStream.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An {@link OutputStream} that starts buffering to a byte array, but + * switches to file buffering once the data reaches a configurable size. + * + *

This class is thread-safe. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class FileBackedOutputStream extends OutputStream { + + private final int fileThreshold; + private final boolean resetOnFinalize; + private final InputSupplier supplier; + + private OutputStream out; + private MemoryOutput memory; + private File file; + + /** ByteArrayOutputStream that exposes its internals. */ + private static class MemoryOutput extends ByteArrayOutputStream { + byte[] getBuffer() { + return buf; + } + + int getCount() { + return count; + } + } + + /** Returns the file holding the data (possibly null). */ + @VisibleForTesting synchronized File getFile() { + return file; + } + + /** + * Creates a new instance that uses the given file threshold, and does + * not reset the data when the {@link InputSupplier} returned by + * {@link #getSupplier} is finalized. + * + * @param fileThreshold the number of bytes before the stream should + * switch to buffering to a file + */ + public FileBackedOutputStream(int fileThreshold) { + this(fileThreshold, false); + } + + /** + * Creates a new instance that uses the given file threshold, and + * optionally resets the data when the {@link InputSupplier} returned + * by {@link #getSupplier} is finalized. + * + * @param fileThreshold the number of bytes before the stream should + * switch to buffering to a file + * @param resetOnFinalize if true, the {@link #reset} method will + * be called when the {@link InputSupplier} returned by {@link + * #getSupplier} is finalized + */ + public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { + this.fileThreshold = fileThreshold; + this.resetOnFinalize = resetOnFinalize; + memory = new MemoryOutput(); + out = memory; + + if (resetOnFinalize) { + supplier = new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return openStream(); + } + + @Override protected void finalize() { + try { + reset(); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + } + }; + } else { + supplier = new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return openStream(); + } + }; + } + } + + /** + * Returns a supplier that may be used to retrieve the data buffered + * by this stream. + */ + public InputSupplier getSupplier() { + return supplier; + } + + private synchronized InputStream openStream() throws IOException { + if (file != null) { + return new FileInputStream(file); + } else { + return new ByteArrayInputStream( + memory.getBuffer(), 0, memory.getCount()); + } + } + + /** + * Calls {@link #close} if not already closed, and then resets this + * object back to its initial state, for reuse. If data was buffered + * to a file, it will be deleted. + * + * @throws IOException if an I/O error occurred while deleting the file buffer + */ + public synchronized void reset() throws IOException { + try { + close(); + } finally { + if (memory == null) { + memory = new MemoryOutput(); + } else { + memory.reset(); + } + out = memory; + if (file != null) { + File deleteMe = file; + file = null; + if (!deleteMe.delete()) { + throw new IOException("Could not delete: " + deleteMe); + } + } + } + } + + @Override public synchronized void write(int b) throws IOException { + update(1); + out.write(b); + } + + @Override public synchronized void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override public synchronized void write(byte[] b, int off, int len) + throws IOException { + update(len); + out.write(b, off, len); + } + + @Override public synchronized void close() throws IOException { + out.close(); + } + + @Override public synchronized void flush() throws IOException { + out.flush(); + } + + /** + * Checks if writing {@code len} bytes would go over threshold, and + * switches to file buffering if so. + */ + private void update(int len) throws IOException { + if (file == null && (memory.getCount() + len > fileThreshold)) { + File temp = File.createTempFile("FileBackedOutputStream", null); + if (resetOnFinalize) { + // Finalizers are not guaranteed to be called on system shutdown; + // this is insurance. + temp.deleteOnExit(); + } + FileOutputStream transfer = new FileOutputStream(temp); + transfer.write(memory.getBuffer(), 0, memory.getCount()); + transfer.flush(); + + // We've successfully transferred the data; switch to writing to file + out = transfer; + file = temp; + memory = null; + } + } +} diff --git a/guava/src/com/google/common/io/Files.java b/guava/src/com/google/common/io/Files.java new file mode 100644 index 0000000..71b5dee --- /dev/null +++ b/guava/src/com/google/common/io/Files.java @@ -0,0 +1,780 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.Checksum; + +/** + * Provides utility methods for working with files. + * + *

All method parameters must be non-null unless documented otherwise. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class Files { + + /** Maximum loop count when creating temp directories. */ + private static final int TEMP_DIR_ATTEMPTS = 10000; + + private Files() {} + + /** + * Returns a buffered reader that reads from a file using the given + * character set. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return the buffered reader + */ + public static BufferedReader newReader(File file, Charset charset) + throws FileNotFoundException { + return new BufferedReader( + new InputStreamReader(new FileInputStream(file), charset)); + } + + /** + * Returns a buffered writer that writes to a file using the given + * character set. + * + * @param file the file to write to + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @return the buffered writer + */ + public static BufferedWriter newWriter(File file, Charset charset) + throws FileNotFoundException { + return new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), charset)); + } + + /** + * Returns a factory that will supply instances of {@link FileInputStream} + * that read from a file. + * + * @param file the file to read from + * @return the factory + */ + public static InputSupplier newInputStreamSupplier( + final File file) { + Preconditions.checkNotNull(file); + return new InputSupplier() { + @Override + public FileInputStream getInput() throws IOException { + return new FileInputStream(file); + } + }; + } + + /** + * Returns a factory that will supply instances of {@link FileOutputStream} + * that write to a file. + * + * @param file the file to write to + * @return the factory + */ + public static OutputSupplier newOutputStreamSupplier( + File file) { + return newOutputStreamSupplier(file, false); + } + + /** + * Returns a factory that will supply instances of {@link FileOutputStream} + * that write to or append to a file. + * + * @param file the file to write to + * @param append if true, the encoded characters will be appended to the file; + * otherwise the file is overwritten + * @return the factory + */ + public static OutputSupplier newOutputStreamSupplier( + final File file, final boolean append) { + Preconditions.checkNotNull(file); + return new OutputSupplier() { + @Override + public FileOutputStream getOutput() throws IOException { + return new FileOutputStream(file, append); + } + }; + } + + /** + * Returns a factory that will supply instances of + * {@link InputStreamReader} that read a file using the given character set. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return the factory + */ + public static InputSupplier newReaderSupplier(File file, + Charset charset) { + return CharStreams.newReaderSupplier(newInputStreamSupplier(file), charset); + } + + /** + * Returns a factory that will supply instances of {@link OutputStreamWriter} + * that write to a file using the given character set. + * + * @param file the file to write to + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @return the factory + */ + public static OutputSupplier newWriterSupplier(File file, + Charset charset) { + return newWriterSupplier(file, charset, false); + } + + /** + * Returns a factory that will supply instances of {@link OutputStreamWriter} + * that write to or append to a file using the given character set. + * + * @param file the file to write to + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @param append if true, the encoded characters will be appended to the file; + * otherwise the file is overwritten + * @return the factory + */ + public static OutputSupplier newWriterSupplier(File file, + Charset charset, boolean append) { + return CharStreams.newWriterSupplier(newOutputStreamSupplier(file, append), + charset); + } + + /** + * Reads all bytes from a file into a byte array. + * + * @param file the file to read from + * @return a byte array containing all the bytes from file + * @throws IllegalArgumentException if the file is bigger than the largest + * possible byte array (2^31 - 1) + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(File file) throws IOException { + Preconditions.checkArgument(file.length() <= Integer.MAX_VALUE); + if (file.length() == 0) { + // Some special files are length 0 but have content nonetheless. + return ByteStreams.toByteArray(newInputStreamSupplier(file)); + } else { + // Avoid an extra allocation and copy. + byte[] b = new byte[(int) file.length()]; + boolean threw = true; + InputStream in = new FileInputStream(file); + try { + ByteStreams.readFully(in, b); + threw = false; + } finally { + Closeables.close(in, threw); + } + return b; + } + } + + /** + * Reads all characters from a file into a {@link String}, using the given + * character set. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return a string containing all the characters from the file + * @throws IOException if an I/O error occurs + */ + public static String toString(File file, Charset charset) throws IOException { + return new String(toByteArray(file), charset.name()); + } + + /** + * Copies to a file all bytes from an {@link InputStream} supplied by a + * factory. + * + * @param from the input factory + * @param to the destination file + * @throws IOException if an I/O error occurs + */ + public static void copy(InputSupplier from, File to) + throws IOException { + ByteStreams.copy(from, newOutputStreamSupplier(to)); + } + + /** + * Overwrites a file with the contents of a byte array. + * + * @param from the bytes to write + * @param to the destination file + * @throws IOException if an I/O error occurs + */ + public static void write(byte[] from, File to) throws IOException { + ByteStreams.write(from, newOutputStreamSupplier(to)); + } + + /** + * Copies all bytes from a file to an {@link OutputStream} supplied by + * a factory. + * + * @param from the source file + * @param to the output factory + * @throws IOException if an I/O error occurs + */ + public static void copy(File from, OutputSupplier to) + throws IOException { + ByteStreams.copy(newInputStreamSupplier(from), to); + } + + /** + * Copies all bytes from a file to an output stream. + * + * @param from the source file + * @param to the output stream + * @throws IOException if an I/O error occurs + */ + public static void copy(File from, OutputStream to) throws IOException { + ByteStreams.copy(newInputStreamSupplier(from), to); + } + + /** + * Copies all the bytes from one file to another. + *. + * @param from the source file + * @param to the destination file + * @throws IOException if an I/O error occurs + * @throws IllegalArgumentException if {@code from.equals(to)} + */ + public static void copy(File from, File to) throws IOException { + Preconditions.checkArgument(!from.equals(to), + "Source %s and destination %s must be different", from, to); + copy(newInputStreamSupplier(from), to); + } + + /** + * Copies to a file all characters from a {@link Readable} and + * {@link Closeable} object supplied by a factory, using the given + * character set. + * + * @param from the readable supplier + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @throws IOException if an I/O error occurs + */ + public static void copy( + InputSupplier from, File to, Charset charset) throws IOException { + CharStreams.copy(from, newWriterSupplier(to, charset)); + } + + /** + * Writes a character sequence (such as a string) to a file using the given + * character set. + * + * @param from the character sequence to write + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @throws IOException if an I/O error occurs + */ + public static void write(CharSequence from, File to, Charset charset) + throws IOException { + write(from, to, charset, false); + } + + /** + * Appends a character sequence (such as a string) to a file using the given + * character set. + * + * @param from the character sequence to append + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @throws IOException if an I/O error occurs + */ + public static void append(CharSequence from, File to, Charset charset) + throws IOException { + write(from, to, charset, true); + } + + /** + * Private helper method. Writes a character sequence to a file, + * optionally appending. + * + * @param from the character sequence to append + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link + * Charsets} for helpful predefined constants + * @param append true to append, false to overwrite + * @throws IOException if an I/O error occurs + */ + private static void write(CharSequence from, File to, Charset charset, + boolean append) throws IOException { + CharStreams.write(from, newWriterSupplier(to, charset, append)); + } + + /** + * Copies all characters from a file to a {@link Appendable} & + * {@link Closeable} object supplied by a factory, using the given + * character set. + * + * @param from the source file + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @param to the appendable supplier + * @throws IOException if an I/O error occurs + */ + public static void copy(File from, + Charset charset, OutputSupplier to) throws IOException { + CharStreams.copy(newReaderSupplier(from, charset), to); + } + + /** + * Copies all characters from a file to an appendable object, + * using the given character set. + * + * @param from the source file + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @param to the appendable object + * @throws IOException if an I/O error occurs + */ + public static void copy(File from, Charset charset, Appendable to) + throws IOException { + CharStreams.copy(newReaderSupplier(from, charset), to); + } + + /** + * Returns true if the files contains the same bytes. + * + * @throws IOException if an I/O error occurs + */ + public static boolean equal(File file1, File file2) throws IOException { + if (file1 == file2 || file1.equals(file2)) { + return true; + } + + /* + * Some operating systems may return zero as the length for files + * denoting system-dependent entities such as devices or pipes, in + * which case we must fall back on comparing the bytes directly. + */ + long len1 = file1.length(); + long len2 = file2.length(); + if (len1 != 0 && len2 != 0 && len1 != len2) { + return false; + } + return ByteStreams.equal(newInputStreamSupplier(file1), + newInputStreamSupplier(file2)); + } + + /** + * Atomically creates a new directory somewhere beneath the system's + * temporary directory (as defined by the {@code java.io.tmpdir} system + * property), and returns its name. + * + *

Use this method instead of {@link File#createTempFile(String, String)} + * when you wish to create a directory, not a regular file. A common pitfall + * is to call {@code createTempFile}, delete the file and create a + * directory in its place, but this leads a race condition which can be + * exploited to create security vulnerabilities, especially when executable + * files are to be written into the directory. + * + *

This method assumes that the temporary volume is writable, has free + * inodes and free blocks, and that it will not be called thousands of times + * per second. + * + * @return the newly-created directory + * @throws IllegalStateException if the directory could not be created + */ + public static File createTempDir() { + File baseDir = new File(System.getProperty("java.io.tmpdir")); + String baseName = System.currentTimeMillis() + "-"; + + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + return tempDir; + } + } + throw new IllegalStateException("Failed to create directory within " + + TEMP_DIR_ATTEMPTS + " attempts (tried " + + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); + } + + /** + * Creates an empty file or updates the last updated timestamp on the + * same as the unix command of the same name. + * + * @param file the file to create or update + * @throws IOException if an I/O error occurs + */ + public static void touch(File file) throws IOException { + if (!file.createNewFile() + && !file.setLastModified(System.currentTimeMillis())) { + throw new IOException("Unable to update modification time of " + file); + } + } + + /** + * Creates any necessary but nonexistent parent directories of the specified + * file. Note that if this operation fails it may have succeeded in creating + * some (but not all) of the necessary parent directories. + * + * @throws IOException if an I/O error occurs, or if any necessary but + * nonexistent parent directories of the specified file could not be + * created. + * @since 4.0 + */ + public static void createParentDirs(File file) throws IOException { + File parent = file.getCanonicalFile().getParentFile(); + if (parent == null) { + /* + * The given directory is a filesystem root. All zero of its ancestors + * exist. This doesn't mean that the root itself exists -- consider x:\ on + * a Windows machine without such a drive -- or even that the caller can + * create it, but this method makes no such guarantees even for non-root + * files. + */ + return; + } + parent.mkdirs(); + if (!parent.isDirectory()) { + throw new IOException("Unable to create parent directories of " + file); + } + } + + /** + * Moves the file from one path to another. This method can rename a file or + * move it to a different directory, like the Unix {@code mv} command. + * + * @param from the source file + * @param to the destination file + * @throws IOException if an I/O error occurs + * @throws IllegalArgumentException if {@code from.equals(to)} + */ + public static void move(File from, File to) throws IOException { + Preconditions.checkNotNull(to); + Preconditions.checkArgument(!from.equals(to), + "Source %s and destination %s must be different", from, to); + + if (!from.renameTo(to)) { + copy(from, to); + if (!from.delete()) { + if (!to.delete()) { + throw new IOException("Unable to delete " + to); + } + throw new IOException("Unable to delete " + from); + } + } + } + + /** + * Reads the first line from a file. The line does not include + * line-termination characters, but does include other leading and + * trailing whitespace. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return the first line, or null if the file is empty + * @throws IOException if an I/O error occurs + */ + public static String readFirstLine(File file, Charset charset) + throws IOException { + return CharStreams.readFirstLine(Files.newReaderSupplier(file, charset)); + } + + /** + * Reads all of the lines from a file. The lines do not include + * line-termination characters, but do include other leading and + * trailing whitespace. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines(File file, Charset charset) + throws IOException { + return CharStreams.readLines(Files.newReaderSupplier(file, charset)); + } + + /** + * Streams lines from a {@link File}, stopping when our callback returns + * false, or we have read all of the lines. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @param callback the {@link LineProcessor} to use to handle the lines + * @return the output of processing the lines + * @throws IOException if an I/O error occurs + */ + public static T readLines(File file, Charset charset, + LineProcessor callback) throws IOException { + return CharStreams.readLines(Files.newReaderSupplier(file, charset), + callback); + } + + /** + * Process the bytes of a file. + * + *

(If this seems too complicated, maybe you're looking for + * {@link #toByteArray}.) + * + * @param file the file to read + * @param processor the object to which the bytes of the file are passed. + * @return the result of the byte processor + * @throws IOException if an I/O error occurs + */ + public static T readBytes(File file, ByteProcessor processor) + throws IOException { + return ByteStreams.readBytes(newInputStreamSupplier(file), processor); + } + + /** + * Computes and returns the checksum value for a file. + * The checksum object is reset when this method returns successfully. + * + * @param file the file to read + * @param checksum the checksum object + * @return the result of {@link Checksum#getValue} after updating the + * checksum object with all of the bytes in the file + * @throws IOException if an I/O error occurs + */ + public static long getChecksum(File file, Checksum checksum) + throws IOException { + return ByteStreams.getChecksum(newInputStreamSupplier(file), checksum); + } + + /** + * Computes the hash code of the {@code file} using {@code hashFunction}. + * + * @param file the file to read + * @param hashFunction the hash function to use to hash the data + * @return the {@link HashCode} of all of the bytes in the file + * @throws IOException if an I/O error occurs + * @since 12.0 + */ + public static HashCode hash(File file, HashFunction hashFunction) + throws IOException { + return ByteStreams.hash(newInputStreamSupplier(file), hashFunction); + } + + /** + * Fully maps a file read-only in to memory as per + * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}. + * + *

Files are mapped from offset 0 to its length. + * + *

This only works for files <= {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @return a read-only buffer reflecting {@code file} + * @throws FileNotFoundException if the {@code file} does not exist + * @throws IOException if an I/O error occurs + * + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + public static MappedByteBuffer map(File file) throws IOException { + return map(file, MapMode.READ_ONLY); + } + + /** + * Fully maps a file in to memory as per + * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)} + * using the requested {@link MapMode}. + * + *

Files are mapped from offset 0 to its length. + * + *

This only works for files <= {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @param mode the mode to use when mapping {@code file} + * @return a buffer reflecting {@code file} + * @throws FileNotFoundException if the {@code file} does not exist + * @throws IOException if an I/O error occurs + * + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + public static MappedByteBuffer map(File file, MapMode mode) + throws IOException { + if (!file.exists()) { + throw new FileNotFoundException(file.toString()); + } + return map(file, mode, file.length()); + } + + /** + * Maps a file in to memory as per + * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)} + * using the requested {@link MapMode}. + * + *

Files are mapped from offset 0 to {@code size}. + * + *

If the mode is {@link MapMode#READ_WRITE} and the file does not exist, + * it will be created with the requested {@code size}. Thus this method is + * useful for creating memory mapped files which do not yet exist. + * + *

This only works for files <= {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @param mode the mode to use when mapping {@code file} + * @return a buffer reflecting {@code file} + * @throws IOException if an I/O error occurs + * + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + public static MappedByteBuffer map(File file, MapMode mode, long size) + throws FileNotFoundException, IOException { + RandomAccessFile raf = + new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw"); + + boolean threw = true; + try { + MappedByteBuffer mbb = map(raf, mode, size); + threw = false; + return mbb; + } finally { + Closeables.close(raf, threw); + } + } + + private static MappedByteBuffer map(RandomAccessFile raf, MapMode mode, + long size) throws IOException { + FileChannel channel = raf.getChannel(); + + boolean threw = true; + try { + MappedByteBuffer mbb = channel.map(mode, 0, size); + threw = false; + return mbb; + } finally { + Closeables.close(channel, threw); + } + } + + /** + * Returns the lexically cleaned form of the path name, usually (but + * not always) equivalent to the original. The following heuristics are used: + * + *

    + *
  • empty string becomes . + *
  • . stays as . + *
  • fold out ./ + *
  • fold out ../ when possible + *
  • collapse multiple slashes + *
  • delete trailing slashes (unless the path is just "/") + *
+ * + * These heuristics do not always match the behavior of the filesystem. In + * particular, consider the path {@code a/../b}, which {@code simplifyPath} + * will change to {@code b}. If {@code a} is a symlink to {@code x}, {@code + * a/../b} may refer to a sibling of {@code x}, rather than the sibling of + * {@code a} referred to by {@code b}. + * + * @since 11.0 + */ + public static String simplifyPath(String pathname) { + if (pathname.length() == 0) { + return "."; + } + + // split the path apart + Iterable components = + Splitter.on('/').omitEmptyStrings().split(pathname); + List path = new ArrayList(); + + // resolve ., .., and // + for (String component : components) { + if (component.equals(".")) { + continue; + } else if (component.equals("..")) { + if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) { + path.remove(path.size() - 1); + } else { + path.add(".."); + } + } else { + path.add(component); + } + } + + // put it back together + String result = Joiner.on('/').join(path); + if (pathname.charAt(0) == '/') { + result = "/" + result; + } + + while (result.startsWith("/../")) { + result = result.substring(3); + } + if (result.equals("/..")) { + result = "/"; + } else if ("".equals(result)) { + result = "."; + } + + return result; + } + + /** + * Returns the file + * extension for the given file name, or the empty string if the file has + * no extension. The result does not include the '{@code .}'. + * + * @since 11.0 + */ + public static String getFileExtension(String fileName) { + checkNotNull(fileName); + int dotIndex = fileName.lastIndexOf('.'); + return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); + } +} diff --git a/guava/src/com/google/common/io/Flushables.java b/guava/src/com/google/common/io/Flushables.java new file mode 100644 index 0000000..68ff860 --- /dev/null +++ b/guava/src/com/google/common/io/Flushables.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.Flushable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility methods for working with {@link Flushable} objects. + * + * @author Michael Lancaster + * @since 1.0 + */ +@Beta +public final class Flushables { + private static final Logger logger + = Logger.getLogger(Flushables.class.getName()); + + private Flushables() {} + + /** + * Flush a {@link Flushable}, with control over whether an + * {@code IOException} may be thrown. + * + *

If {@code swallowIOException} is true, then we don't rethrow + * {@code IOException}, but merely log it. + * + * @param flushable the {@code Flushable} object to be flushed. + * @param swallowIOException if true, don't propagate IO exceptions + * thrown by the {@code flush} method + * @throws IOException if {@code swallowIOException} is false and + * {@link Flushable#flush} throws an {@code IOException}. + * @see Closeables#close + */ + public static void flush(Flushable flushable, boolean swallowIOException) + throws IOException { + try { + flushable.flush(); + } catch (IOException e) { + if (swallowIOException) { + logger.log(Level.WARNING, + "IOException thrown while flushing Flushable.", e); + } else { + throw e; + } + } + } + + /** + * Equivalent to calling {@code flush(flushable, true)}, but with no + * {@code IOException} in the signature. + * + * @param flushable the {@code Flushable} object to be flushed. + */ + public static void flushQuietly(Flushable flushable) { + try { + flush(flushable, true); + } catch (IOException e) { + logger.log(Level.SEVERE, "IOException should not have been thrown.", e); + } + } +} diff --git a/guava/src/com/google/common/io/InputSupplier.java b/guava/src/com/google/common/io/InputSupplier.java new file mode 100644 index 0000000..2b6a4ad --- /dev/null +++ b/guava/src/com/google/common/io/InputSupplier.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.IOException; + +/** + * A factory for readable streams of bytes or characters. + * + * @author Chris Nokleberg + * @since 1.0 + */ +public interface InputSupplier { + + /** + * Returns an object that encapsulates a readable resource. + *

+ * Like {@link Iterable#iterator}, this method may be called repeatedly to + * get independent channels to the same underlying resource. + *

+ * Where the channel maintains a position within the resource, moving that + * cursor within one channel should not affect the starting position of + * channels returned by other calls. + */ + T getInput() throws IOException; +} diff --git a/guava/src/com/google/common/io/LimitInputStream.java b/guava/src/com/google/common/io/LimitInputStream.java new file mode 100644 index 0000000..e9d963d --- /dev/null +++ b/guava/src/com/google/common/io/LimitInputStream.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An InputStream that limits the number of bytes which can be read. + * + * @author Charles Fry + * @since 1.0 + */ +@Beta +public final class LimitInputStream extends FilterInputStream { + + private long left; + private long mark = -1; + + /** + * Wraps another input stream, limiting the number of bytes which can be read. + * + * @param in the input stream to be wrapped + * @param limit the maximum number of bytes to be read + */ + public LimitInputStream(InputStream in, long limit) { + super(in); + Preconditions.checkNotNull(in); + Preconditions.checkArgument(limit >= 0, "limit must be non-negative"); + left = limit; + } + + @Override public int available() throws IOException { + return (int) Math.min(in.available(), left); + } + + @Override public synchronized void mark(int readlimit) { + in.mark(readlimit); + mark = left; + // it's okay to mark even if mark isn't supported, as reset won't work + } + + @Override public int read() throws IOException { + if (left == 0) { + return -1; + } + + int result = in.read(); + if (result != -1) { + --left; + } + return result; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + if (left == 0) { + return -1; + } + + len = (int) Math.min(len, left); + int result = in.read(b, off, len); + if (result != -1) { + left -= result; + } + return result; + } + + @Override public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + left = mark; + } + + @Override public long skip(long n) throws IOException { + n = Math.min(n, left); + long skipped = in.skip(n); + left -= skipped; + return skipped; + } +} diff --git a/guava/src/com/google/common/io/LineBuffer.java b/guava/src/com/google/common/io/LineBuffer.java new file mode 100644 index 0000000..1f1c8dc --- /dev/null +++ b/guava/src/com/google/common/io/LineBuffer.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.IOException; + +/** + * Package-protected abstract class that implements the line reading + * algorithm used by {@link LineReader}. Line separators are per {@link + * java.io.BufferedReader}: line feed, carriage return, or carriage + * return followed immediately by a linefeed. + * + *

Subclasses must implement {@link #handleLine}, call {@link #add} + * to pass character data, and call {@link #finish} at the end of stream. + * + * @author Chris Nokleberg + * @since 1.0 + */ +abstract class LineBuffer { + /** Holds partial line contents. */ + private StringBuilder line = new StringBuilder(); + /** Whether a line ending with a CR is pending processing. */ + private boolean sawReturn; + + /** + * Process additional characters from the stream. When a line separator + * is found the contents of the line and the line separator itself + * are passed to the abstract {@link #handleLine} method. + * + * @param cbuf the character buffer to process + * @param off the offset into the buffer + * @param len the number of characters to process + * @throws IOException if an I/O error occurs + * @see #finish + */ + protected void add(char[] cbuf, int off, int len) throws IOException { + int pos = off; + if (sawReturn && len > 0) { + // Last call to add ended with a CR; we can handle the line now. + if (finishLine(cbuf[pos] == '\n')) { + pos++; + } + } + + int start = pos; + for (int end = off + len; pos < end; pos++) { + switch (cbuf[pos]) { + case '\r': + line.append(cbuf, start, pos - start); + sawReturn = true; + if (pos + 1 < end) { + if (finishLine(cbuf[pos + 1] == '\n')) { + pos++; + } + } + start = pos + 1; + break; + + case '\n': + line.append(cbuf, start, pos - start); + finishLine(true); + start = pos + 1; + break; + } + } + line.append(cbuf, start, off + len - start); + } + + /** Called when a line is complete. */ + private boolean finishLine(boolean sawNewline) throws IOException { + handleLine(line.toString(), sawReturn + ? (sawNewline ? "\r\n" : "\r") + : (sawNewline ? "\n" : "")); + line = new StringBuilder(); + sawReturn = false; + return sawNewline; + } + + /** + * Subclasses must call this method after finishing character processing, + * in order to ensure that any unterminated line in the buffer is + * passed to {@link #handleLine}. + * + * @throws IOException if an I/O error occurs + */ + protected void finish() throws IOException { + if (sawReturn || line.length() > 0) { + finishLine(false); + } + } + + /** + * Called for each line found in the character data passed to + * {@link #add}. + * + * @param line a line of text (possibly empty), without any line separators + * @param end the line separator; one of {@code "\r"}, {@code "\n"}, + * {@code "\r\n"}, or {@code ""} + * @throws IOException if an I/O error occurs + */ + protected abstract void handleLine(String line, String end) + throws IOException; +} diff --git a/guava/src/com/google/common/io/LineProcessor.java b/guava/src/com/google/common/io/LineProcessor.java new file mode 100644 index 0000000..9ee6a0f --- /dev/null +++ b/guava/src/com/google/common/io/LineProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.IOException; + +/** + * A callback to be used with the streaming {@code readLines} methods. + * + *

{@link #processLine} will be called for each line that is read, and + * should return {@code false} when you want to stop processing. + * + * @author Miles Barr + * @since 1.0 + */ +@Beta +public interface LineProcessor { + + /** + * This method will be called once for each line. + * + * @param line the line read from the input, without delimiter + * @return true to continue processing, false to stop + */ + boolean processLine(String line) throws IOException; + + /** Return the result of processing all the lines. */ + T getResult(); +} diff --git a/guava/src/com/google/common/io/LineReader.java b/guava/src/com/google/common/io/LineReader.java new file mode 100644 index 0000000..69ae0cf --- /dev/null +++ b/guava/src/com/google/common/io/LineReader.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; +import java.util.LinkedList; +import java.util.Queue; + +/** + * A class for reading lines of text. Provides the same functionality + * as {@link java.io.BufferedReader#readLine()} but for all {@link Readable} + * objects, not just instances of {@link Reader}. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class LineReader { + private final Readable readable; + private final Reader reader; + private final char[] buf = new char[0x1000]; // 4K + private final CharBuffer cbuf = CharBuffer.wrap(buf); + + private final Queue lines = new LinkedList(); + private final LineBuffer lineBuf = new LineBuffer() { + @Override protected void handleLine(String line, String end) { + lines.add(line); + } + }; + + /** + * Creates a new instance that will read lines from the given + * {@code Readable} object. + */ + public LineReader(Readable readable) { + Preconditions.checkNotNull(readable); + this.readable = readable; + this.reader = (readable instanceof Reader) ? (Reader) readable : null; + } + + /** + * Reads a line of text. A line is considered to be terminated by any + * one of a line feed ({@code '\n'}), a carriage return + * ({@code '\r'}), or a carriage return followed immediately by a linefeed + * ({@code "\r\n"}). + * + * @return a {@code String} containing the contents of the line, not + * including any line-termination characters, or {@code null} if the + * end of the stream has been reached. + * @throws IOException if an I/O error occurs + */ + public String readLine() throws IOException { + while (lines.peek() == null) { + cbuf.clear(); + // The default implementation of Reader#read(CharBuffer) allocates a + // temporary char[], so we call Reader#read(char[], int, int) instead. + int read = (reader != null) + ? reader.read(buf, 0, buf.length) + : readable.read(cbuf); + if (read == -1) { + lineBuf.finish(); + break; + } + lineBuf.add(buf, 0, read); + } + return lines.poll(); + } +} diff --git a/guava/src/com/google/common/io/LittleEndianDataInputStream.java b/guava/src/com/google/common/io/LittleEndianDataInputStream.java new file mode 100644 index 0000000..e60d22f --- /dev/null +++ b/guava/src/com/google/common/io/LittleEndianDataInputStream.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An implementation of {@link DataInput} that uses little-endian byte ordering + * for reading {@code short}, {@code int}, {@code float}, {@code double}, and + * {@code long} values. + *

+ * Note: This class intentionally violates the specification of its + * supertype {@code DataInput}, which explicitly requires big-endian byte order. + * + * @author Chris Nokleberg + * @author Keith Bottner + * @since 8.0 + */ +@Beta +public final class LittleEndianDataInputStream extends FilterInputStream + implements DataInput { + + /** + * Creates a {@code LittleEndianDataInputStream} that wraps the given stream. + * + * @param in the stream to delegate to + */ + public LittleEndianDataInputStream(InputStream in) { + super(Preconditions.checkNotNull(in)); + } + + /** + * This method will throw an {@link UnsupportedOperationException}. + */ + @Override + public String readLine() { + throw new UnsupportedOperationException("readLine is not supported"); + } + + @Override + public void readFully(byte[] b) throws IOException { + ByteStreams.readFully(this, b); + } + + @Override + public void readFully(byte[] b, int off, int len) throws IOException { + ByteStreams.readFully(this, b, off, len); + } + + @Override + public int skipBytes(int n) throws IOException { + return (int) in.skip(n); + } + + @Override + public int readUnsignedByte() throws IOException { + int b1 = in.read(); + if (0 > b1) { + throw new EOFException(); + } + + return b1; + } + + /** + * Reads an unsigned {@code short} as specified by + * {@link DataInputStream#readUnsignedShort()}, except using little-endian + * byte order. + * + * @return the next two bytes of the input stream, interpreted as an + * unsigned 16-bit integer in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public int readUnsignedShort() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + + return Ints.fromBytes((byte) 0, (byte) 0, b2, b1); + } + + /** + * Reads an integer as specified by {@link DataInputStream#readInt()}, except + * using little-endian byte order. + * + * @return the next four bytes of the input stream, interpreted as an + * {@code int} in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public int readInt() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + byte b3 = readAndCheckByte(); + byte b4 = readAndCheckByte(); + + return Ints.fromBytes( b4, b3, b2, b1); + } + + /** + * Reads a {@code long} as specified by {@link DataInputStream#readLong()}, + * except using little-endian byte order. + * + * @return the next eight bytes of the input stream, interpreted as a + * {@code long} in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public long readLong() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + byte b3 = readAndCheckByte(); + byte b4 = readAndCheckByte(); + byte b5 = readAndCheckByte(); + byte b6 = readAndCheckByte(); + byte b7 = readAndCheckByte(); + byte b8 = readAndCheckByte(); + + return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1); + } + + /** + * Reads a {@code float} as specified by {@link DataInputStream#readFloat()}, + * except using little-endian byte order. + * + * @return the next four bytes of the input stream, interpreted as a + * {@code float} in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads a {@code double} as specified by + * {@link DataInputStream#readDouble()}, except using little-endian byte + * order. + * + * @return the next eight bytes of the input stream, interpreted as a + * {@code double} in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + @Override + public String readUTF() throws IOException { + return new DataInputStream(in).readUTF(); + } + + /** + * Reads a {@code short} as specified by {@link DataInputStream#readShort()}, + * except using little-endian byte order. + * + * @return the next two bytes of the input stream, interpreted as a + * {@code short} in little-endian byte order. + * @throws IOException if an I/O error occurs. + */ + @Override + public short readShort() throws IOException { + return (short) readUnsignedShort(); + } + + /** + * Reads a char as specified by {@link DataInputStream#readChar()}, except + * using little-endian byte order. + * + * @return the next two bytes of the input stream, interpreted as a + * {@code char} in little-endian byte order + * @throws IOException if an I/O error occurs + */ + @Override + public char readChar() throws IOException { + return (char) readUnsignedShort(); + } + + @Override + public byte readByte() throws IOException { + return (byte) readUnsignedByte(); + } + + @Override + public boolean readBoolean() throws IOException { + return readUnsignedByte() != 0; + } + + /** + * Reads a byte from the input stream checking that the end of file (EOF) + * has not been encountered. + * + * @return byte read from input + * @throws IOException if an error is encountered while reading + * @throws EOFException if the end of file (EOF) is encountered. + */ + private byte readAndCheckByte() throws IOException, EOFException { + int b1 = in.read(); + + if (-1 == b1) { + throw new EOFException(); + } + + return (byte) b1; + } + +} diff --git a/guava/src/com/google/common/io/LittleEndianDataOutputStream.java b/guava/src/com/google/common/io/LittleEndianDataOutputStream.java new file mode 100644 index 0000000..1725ae0 --- /dev/null +++ b/guava/src/com/google/common/io/LittleEndianDataOutputStream.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Longs; + +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An implementation of {@link DataOutput} that uses little-endian byte ordering + * for writing {@code char}, {@code short}, {@code int}, {@code float}, {@code + * double}, and {@code long} values. + *

+ * Note: This class intentionally violates the specification of its + * supertype {@code DataOutput}, which explicitly requires big-endian byte + * order. + * + * @author Chris Nokleberg + * @author Keith Bottner + * @since 8.0 + */ +@Beta +public class LittleEndianDataOutputStream extends FilterOutputStream + implements DataOutput { + + /** + * Creates a {@code LittleEndianDataOutputStream} that wraps the given stream. + * + * @param out the stream to delegate to + */ + public LittleEndianDataOutputStream(OutputStream out) { + super(new DataOutputStream(Preconditions.checkNotNull(out))); + } + + @Override public void write(byte[] b, int off, int len) throws IOException { + // Override slow FilterOutputStream impl + out.write(b, off, len); + } + + @Override public void writeBoolean(boolean v) throws IOException { + ((DataOutputStream) out).writeBoolean(v); + } + + @Override public void writeByte(int v) throws IOException { + ((DataOutputStream) out).writeByte(v); + } + + /** + * @deprecated The semantics of {@code writeBytes(String s)} are considered + * dangerous. Please use {@link #writeUTF(String s)}, + * {@link #writeChars(String s)} or another write method instead. + */ + @Deprecated + @Override public void writeBytes(String s) throws IOException { + ((DataOutputStream) out).writeBytes(s); + } + + /** + * Writes a char as specified by {@link DataOutputStream#writeChar(int)}, + * except using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeChar(int v) throws IOException { + writeShort(v); + } + + /** + * Writes a {@code String} as specified by + * {@link DataOutputStream#writeChars(String)}, except each character is + * written using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeChars(String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + writeChar(s.charAt(i)); + } + } + + /** + * Writes a {@code double} as specified by + * {@link DataOutputStream#writeDouble(double)}, except using little-endian + * byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + /** + * Writes a {@code float} as specified by + * {@link DataOutputStream#writeFloat(float)}, except using little-endian byte + * order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + /** + * Writes an {@code int} as specified by + * {@link DataOutputStream#writeInt(int)}, except using little-endian byte + * order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeInt(int v) throws IOException { + out.write(0xFF & v); + out.write(0xFF & (v >> 8)); + out.write(0xFF & (v >> 16)); + out.write(0xFF & (v >> 24)); + } + + /** + * Writes a {@code long} as specified by + * {@link DataOutputStream#writeLong(long)}, except using little-endian byte + * order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeLong(long v) throws IOException { + byte[] bytes = Longs.toByteArray(Long.reverseBytes(v)); + write(bytes, 0, bytes.length); + } + + /** + * Writes a {@code short} as specified by + * {@link DataOutputStream#writeShort(int)}, except using little-endian byte + * order. + * + * @throws IOException if an I/O error occurs + */ + @Override public void writeShort(int v) throws IOException { + out.write(0xFF & v); + out.write(0xFF & (v >> 8)); + } + + @Override public void writeUTF(String str) throws IOException { + ((DataOutputStream) out).writeUTF(str); + } +} diff --git a/guava/src/com/google/common/io/MultiInputStream.java b/guava/src/com/google/common/io/MultiInputStream.java new file mode 100644 index 0000000..f8f1a0b --- /dev/null +++ b/guava/src/com/google/common/io/MultiInputStream.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +/** + * An {@link InputStream} that concatenates multiple substreams. At most + * one stream will be open at a time. + * + * @author Chris Nokleberg + * @since 1.0 + */ +final class MultiInputStream extends InputStream { + + private Iterator> it; + private InputStream in; + + /** + * Creates a new instance. + * + * @param it an iterator of I/O suppliers that will provide each substream + */ + public MultiInputStream( + Iterator> it) + throws IOException { + this.it = it; + advance(); + } + + @Override public void close() throws IOException { + if (in != null) { + try { + in.close(); + } finally { + in = null; + } + } + } + + /** + * Closes the current input stream and opens the next one, if any. + */ + private void advance() throws IOException { + close(); + if (it.hasNext()) { + in = it.next().getInput(); + } + } + + @Override public int available() throws IOException { + if (in == null) { + return 0; + } + return in.available(); + } + + @Override public boolean markSupported() { + return false; + } + + @Override public int read() throws IOException { + if (in == null) { + return -1; + } + int result = in.read(); + if (result == -1) { + advance(); + return read(); + } + return result; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + if (in == null) { + return -1; + } + int result = in.read(b, off, len); + if (result == -1) { + advance(); + return read(b, off, len); + } + return result; + } + + @Override public long skip(long n) throws IOException { + if (in == null || n <= 0) { + return 0; + } + long result = in.skip(n); + if (result != 0) { + return result; + } + if (read() == -1) { + return 0; + } + return 1 + in.skip(n - 1); + } +} diff --git a/guava/src/com/google/common/io/MultiReader.java b/guava/src/com/google/common/io/MultiReader.java new file mode 100644 index 0000000..6757a26 --- /dev/null +++ b/guava/src/com/google/common/io/MultiReader.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; + +/** + * A {@link Reader} that concatenates multiple readers. + * + * @author Bin Zhu + * @since 1.0 + */ +class MultiReader extends Reader { + private final Iterator> it; + private Reader current; + + MultiReader(Iterator> readers) + throws IOException { + this.it = readers; + advance(); + } + + /** + * Closes the current reader and opens the next one, if any. + */ + private void advance() throws IOException { + close(); + if (it.hasNext()) { + current = it.next().getInput(); + } + } + + @Override public int read(char cbuf[], int off, int len) throws IOException { + if (current == null) { + return -1; + } + int result = current.read(cbuf, off, len); + if (result == -1) { + advance(); + return read(cbuf, off, len); + } + return result; + } + + @Override public long skip(long n) throws IOException { + Preconditions.checkArgument(n >= 0, "n is negative"); + if (n > 0) { + while (current != null) { + long result = current.skip(n); + if (result > 0) { + return result; + } + advance(); + } + } + return 0; + } + + @Override public boolean ready() throws IOException { + return (current != null) && current.ready(); + } + + @Override public void close() throws IOException { + if (current != null) { + try { + current.close(); + } finally { + current = null; + } + } + } +} diff --git a/guava/src/com/google/common/io/NullOutputStream.java b/guava/src/com/google/common/io/NullOutputStream.java new file mode 100644 index 0000000..1c1e98e --- /dev/null +++ b/guava/src/com/google/common/io/NullOutputStream.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2004 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; + +import java.io.OutputStream; + +/** + * Implementation of {@link OutputStream} that simply discards written bytes. + * + * @author Spencer Kimball + * @since 1.0 + */ +@Beta +public final class NullOutputStream extends OutputStream { + /** Discards the specified byte. */ + @Override public void write(int b) { + } + + /** Discards the specified byte array. */ + @Override public void write(byte[] b, int off, int len) { + } +} diff --git a/guava/src/com/google/common/io/OutputSupplier.java b/guava/src/com/google/common/io/OutputSupplier.java new file mode 100644 index 0000000..185082c --- /dev/null +++ b/guava/src/com/google/common/io/OutputSupplier.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import java.io.IOException; + +/** + * A factory for writable streams of bytes or characters. + * + * @author Chris Nokleberg + * @since 1.0 + */ +public interface OutputSupplier { + + /** + * Returns an object that encapsulates a writable resource. + *

+ * Like {@link Iterable#iterator}, this method may be called repeatedly to + * get independent channels to the same underlying resource. + *

+ * Where the channel maintains a position within the resource, moving that + * cursor within one channel should not affect the starting position of + * channels returned by other calls. + */ + T getOutput() throws IOException; +} diff --git a/guava/src/com/google/common/io/PatternFilenameFilter.java b/guava/src/com/google/common/io/PatternFilenameFilter.java new file mode 100644 index 0000000..2859277 --- /dev/null +++ b/guava/src/com/google/common/io/PatternFilenameFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.annotation.Nullable; + +/** + * File name filter that only accepts files matching a regular expression. This + * class is thread-safe and immutable. + * + * @author Apple Chow + * @since 1.0 + */ +@Beta +public final class PatternFilenameFilter implements FilenameFilter { + + private final Pattern pattern; + + /** + * Constructs a pattern file name filter object. + * @param patternStr the pattern string on which to filter file names + * + * @throws PatternSyntaxException if pattern compilation fails (runtime) + */ + public PatternFilenameFilter(String patternStr) { + this(Pattern.compile(patternStr)); + } + + /** + * Constructs a pattern file name filter object. + * @param pattern the pattern on which to filter file names + */ + public PatternFilenameFilter(Pattern pattern) { + this.pattern = Preconditions.checkNotNull(pattern); + } + + @Override public boolean accept(@Nullable File dir, String fileName) { + return pattern.matcher(fileName).matches(); + } +} diff --git a/guava/src/com/google/common/io/Resources.java b/guava/src/com/google/common/io/Resources.java new file mode 100644 index 0000000..1bb8067 --- /dev/null +++ b/guava/src/com/google/common/io/Resources.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.List; + +/** + * Provides utility methods for working with resources in the classpath. + * Note that even though these methods use {@link URL} parameters, they + * are usually not appropriate for HTTP or other non-classpath resources. + * + *

All method parameters must be non-null unless documented otherwise. + * + * @author Chris Nokleberg + * @author Ben Yu + * @since 1.0 + */ +@Beta +public final class Resources { + private Resources() {} + + /** + * Returns a factory that will supply instances of {@link InputStream} that + * read from the given URL. + * + * @param url the URL to read from + * @return the factory + */ + public static InputSupplier newInputStreamSupplier( + final URL url) { + checkNotNull(url); + return new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return url.openStream(); + } + }; + } + + /** + * Returns a factory that will supply instances of + * {@link InputStreamReader} that read a URL using the given character set. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return the factory + */ + public static InputSupplier newReaderSupplier( + URL url, Charset charset) { + return CharStreams.newReaderSupplier(newInputStreamSupplier(url), charset); + } + + /** + * Reads all bytes from a URL into a byte array. + * + * @param url the URL to read from + * @return a byte array containing all the bytes from the URL + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(URL url) throws IOException { + return ByteStreams.toByteArray(newInputStreamSupplier(url)); + } + + /** + * Reads all characters from a URL into a {@link String}, using the given + * character set. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return a string containing all the characters from the URL + * @throws IOException if an I/O error occurs. + */ + public static String toString(URL url, Charset charset) throws IOException { + return CharStreams.toString(newReaderSupplier(url, charset)); + } + + /** + * Streams lines from a URL, stopping when our callback returns false, or we + * have read all of the lines. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @param callback the LineProcessor to use to handle the lines + * @return the output of processing the lines + * @throws IOException if an I/O error occurs + */ + public static T readLines(URL url, Charset charset, + LineProcessor callback) throws IOException { + return CharStreams.readLines(newReaderSupplier(url, charset), callback); + } + + /** + * Reads all of the lines from a URL. The lines do not include + * line-termination characters, but do include other leading and trailing + * whitespace. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link + * Charsets} for helpful predefined constants + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines(URL url, Charset charset) + throws IOException { + return CharStreams.readLines(newReaderSupplier(url, charset)); + } + + /** + * Copies all bytes from a URL to an output stream. + * + * @param from the URL to read from + * @param to the output stream + * @throws IOException if an I/O error occurs + */ + public static void copy(URL from, OutputStream to) throws IOException { + ByteStreams.copy(newInputStreamSupplier(from), to); + } + + /** + * Returns a {@code URL} pointing to {@code resourceName} if the resource is + * found in the class path. {@code Resources.class.getClassLoader()} is used + * to locate the resource. + * + * @throws IllegalArgumentException if resource is not found + */ + public static URL getResource(String resourceName) { + URL url = Resources.class.getClassLoader().getResource(resourceName); + checkArgument(url != null, "resource %s not found.", resourceName); + return url; + } + + /** + * Returns a {@code URL} pointing to {@code resourceName} that is relative to + * {@code contextClass}, if the resource is found in the class path. + * + * @throws IllegalArgumentException if resource is not found + */ + public static URL getResource(Class contextClass, String resourceName) { + URL url = contextClass.getResource(resourceName); + checkArgument(url != null, "resource %s relative to %s not found.", + resourceName, contextClass.getName()); + return url; + } +} diff --git a/guava/src/com/google/common/io/package-info.java b/guava/src/com/google/common/io/package-info.java new file mode 100644 index 0000000..f5f83ad --- /dev/null +++ b/guava/src/com/google/common/io/package-info.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains utility methods and classes for working with Java I/O, + * for example input streams, output streams, readers, writers, and files. + * + *

Many of the methods are based on the + * {@link com.google.common.io.InputSupplier} and + * {@link com.google.common.io.OutputSupplier} interfaces. They are used as + * factories for I/O objects that might throw {@link java.io.IOException} when + * being created. The advantage of using a factory is that the helper methods in + * this package can take care of closing the resource properly, even if an + * exception is thrown. The {@link com.google.common.io.ByteStreams}, + * {@link com.google.common.io.CharStreams}, and + * {@link com.google.common.io.Files} classes all have static helper methods to + * create new factories and to work with them. + * + *

This package is a part of the open-source + * Guava libraries. + * + * @author Chris Nokleberg + */ +@ParametersAreNonnullByDefault +package com.google.common.io; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/guava/src/com/google/common/math/BigIntegerMath.java b/guava/src/com/google/common/math/BigIntegerMath.java new file mode 100644 index 0000000..1645627 --- /dev/null +++ b/guava/src/com/google/common/math/BigIntegerMath.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.math.RoundingMode.CEILING; +import static java.math.RoundingMode.FLOOR; +import static java.math.RoundingMode.HALF_EVEN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +/** + * A class for arithmetic on values of type {@code BigInteger}. + * + *

The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

Similar functionality for {@code int} and for {@code long} can be found in + * {@link IntMath} and {@link LongMath} respectively. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class BigIntegerMath { + /** + * Returns {@code true} if {@code x} represents a power of two. + */ + public static boolean isPowerOfTwo(BigInteger x) { + checkNotNull(x); + return x.signum() > 0 && x.getLowestSetBit() == x.bitLength() - 1; + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + public static int log2(BigInteger x, RoundingMode mode) { + checkPositive("x", checkNotNull(x)); + int logFloor = x.bitLength() - 1; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); // fall through + case DOWN: + case FLOOR: + return logFloor; + + case UP: + case CEILING: + return isPowerOfTwo(x) ? logFloor : logFloor + 1; + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + if (logFloor < SQRT2_PRECOMPUTE_THRESHOLD) { + BigInteger halfPower = SQRT2_PRECOMPUTED_BITS.shiftRight( + SQRT2_PRECOMPUTE_THRESHOLD - logFloor); + if (x.compareTo(halfPower) <= 0) { + return logFloor; + } else { + return logFloor + 1; + } + } + /* + * Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + * + * To determine which side of logFloor.5 the logarithm is, we compare x^2 to 2^(2 * + * logFloor + 1). + */ + BigInteger x2 = x.pow(2); + int logX2Floor = x2.bitLength() - 1; + return (logX2Floor < 2 * logFloor + 1) ? logFloor : logFloor + 1; + + default: + throw new AssertionError(); + } + } + + /* + * The maximum number of bits in a square root for which we'll precompute an explicit half power + * of two. This can be any value, but higher values incur more class load time and linearly + * increasing memory consumption. + */ + @VisibleForTesting static final int SQRT2_PRECOMPUTE_THRESHOLD = 256; + + @VisibleForTesting static final BigInteger SQRT2_PRECOMPUTED_BITS = + new BigInteger("16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667322a", 16); + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible("TODO") + @SuppressWarnings("fallthrough") + public static int log10(BigInteger x, RoundingMode mode) { + checkPositive("x", x); + if (fitsInLong(x)) { + return LongMath.log10(x.longValue(), mode); + } + + int approxLog10 = (int) (log2(x, FLOOR) * LN_2 / LN_10); + BigInteger approxPow = BigInteger.TEN.pow(approxLog10); + int approxCmp = approxPow.compareTo(x); + + /* + * We adjust approxLog10 and approxPow until they're equal to floor(log10(x)) and + * 10^floor(log10(x)). + */ + + if (approxCmp > 0) { + /* + * The code is written so that even completely incorrect approximations will still yield the + * correct answer eventually, but in practice this branch should almost never be entered, + * and even then the loop should not run more than once. + */ + do { + approxLog10--; + approxPow = approxPow.divide(BigInteger.TEN); + approxCmp = approxPow.compareTo(x); + } while (approxCmp > 0); + } else { + BigInteger nextPow = BigInteger.TEN.multiply(approxPow); + int nextCmp = nextPow.compareTo(x); + while (nextCmp <= 0) { + approxLog10++; + approxPow = nextPow; + approxCmp = nextCmp; + nextPow = BigInteger.TEN.multiply(approxPow); + nextCmp = nextPow.compareTo(x); + } + } + + int floorLog = approxLog10; + BigInteger floorPow = approxPow; + int floorCmp = approxCmp; + + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(floorCmp == 0); + // fall through + case FLOOR: + case DOWN: + return floorLog; + + case CEILING: + case UP: + return floorPow.equals(x) ? floorLog : floorLog + 1; + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(10) is irrational, log10(x) - floorLog can never be exactly 0.5 + BigInteger x2 = x.pow(2); + BigInteger halfPowerSquared = floorPow.pow(2).multiply(BigInteger.TEN); + return (x2.compareTo(halfPowerSquared) <= 0) ? floorLog : floorLog + 1; + default: + throw new AssertionError(); + } + } + + private static final double LN_10 = Math.log(10); + private static final double LN_2 = Math.log(2); + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and + * {@code sqrt(x)} is not an integer + */ + @GwtIncompatible("TODO") + @SuppressWarnings("fallthrough") + public static BigInteger sqrt(BigInteger x, RoundingMode mode) { + checkNonNegative("x", x); + if (fitsInLong(x)) { + return BigInteger.valueOf(LongMath.sqrt(x.longValue(), mode)); + } + BigInteger sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor.pow(2).equals(x)); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + return sqrtFloor.pow(2).equals(x) ? sqrtFloor : sqrtFloor.add(BigInteger.ONE); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + BigInteger halfSquare = sqrtFloor.pow(2).add(sqrtFloor); + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both + * x and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. + */ + return (halfSquare.compareTo(x) >= 0) ? sqrtFloor : sqrtFloor.add(BigInteger.ONE); + default: + throw new AssertionError(); + } + } + + @GwtIncompatible("TODO") + private static BigInteger sqrtFloor(BigInteger x) { + /* + * Adapted from Hacker's Delight, Figure 11-1. + * + * Using DoubleUtils.bigToDouble, getting a double approximation of x is extremely fast, and + * then we can get a double approximation of the square root. Then, we iteratively improve this + * guess with an application of Newton's method, which sets guess := (guess + (x / guess)) / 2. + * This iteration has the following two properties: + * + * a) every iteration (except potentially the first) has guess >= floor(sqrt(x)). This is + * because guess' is the arithmetic mean of guess and x / guess, sqrt(x) is the geometric mean, + * and the arithmetic mean is always higher than the geometric mean. + * + * b) this iteration converges to floor(sqrt(x)). In fact, the number of correct digits doubles + * with each iteration, so this algorithm takes O(log(digits)) iterations. + * + * We start out with a double-precision approximation, which may be higher or lower than the + * true value. Therefore, we perform at least one Newton iteration to get a guess that's + * definitely >= floor(sqrt(x)), and then continue the iteration until we reach a fixed point. + */ + BigInteger sqrt0; + int log2 = log2(x, FLOOR); + if(log2 < Double.MAX_EXPONENT) { + sqrt0 = sqrtApproxWithDoubles(x); + } else { + int shift = (log2 - DoubleUtils.SIGNIFICAND_BITS) & ~1; // even! + /* + * We have that x / 2^shift < 2^54. Our initial approximation to sqrtFloor(x) will be + * 2^(shift/2) * sqrtApproxWithDoubles(x / 2^shift). + */ + sqrt0 = sqrtApproxWithDoubles(x.shiftRight(shift)).shiftLeft(shift >> 1); + } + BigInteger sqrt1 = sqrt0.add(x.divide(sqrt0)).shiftRight(1); + if (sqrt0.equals(sqrt1)) { + return sqrt0; + } + do { + sqrt0 = sqrt1; + sqrt1 = sqrt0.add(x.divide(sqrt0)).shiftRight(1); + } while (sqrt1.compareTo(sqrt0) < 0); + return sqrt0; + } + + @GwtIncompatible("TODO") + private static BigInteger sqrtApproxWithDoubles(BigInteger x) { + return DoubleMath.roundToBigInteger(Math.sqrt(DoubleUtils.bigToDouble(x)), HALF_EVEN); + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified + * {@code RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @GwtIncompatible("TODO") + public static BigInteger divide(BigInteger p, BigInteger q, RoundingMode mode){ + BigDecimal pDec = new BigDecimal(p); + BigDecimal qDec = new BigDecimal(q); + return pDec.divide(qDec, 0, mode).toBigIntegerExact(); + } + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive + * integers, or {@code 1} if {@code n == 0}. + * + *

Warning: the result takes O(n log n) space, so use cautiously. + * + *

This uses an efficient binary recursive algorithm to compute the factorial + * with balanced multiplies. It also removes all the 2s from the intermediate + * products (shifting them back in at the end). + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static BigInteger factorial(int n) { + checkNonNegative("n", n); + + // If the factorial is small enough, just use LongMath to do it. + if (n < LongMath.FACTORIALS.length) { + return BigInteger.valueOf(LongMath.FACTORIALS[n]); + } + + // Pre-allocate space for our list of intermediate BigIntegers. + int approxSize = IntMath.divide(n * IntMath.log2(n, CEILING), Long.SIZE, CEILING); + ArrayList bignums = new ArrayList(approxSize); + + // Start from the pre-computed maximum long factorial. + int startingNumber = LongMath.FACTORIALS.length; + long product = LongMath.FACTORIALS[startingNumber - 1]; + // Strip off 2s from this value. + int shift = Long.numberOfTrailingZeros(product); + product >>= shift; + + // Use floor(log2(num)) + 1 to prevent overflow of multiplication. + int productBits = LongMath.log2(product, FLOOR) + 1; + int bits = LongMath.log2(startingNumber, FLOOR) + 1; + // Check for the next power of two boundary, to save us a CLZ operation. + int nextPowerOfTwo = 1 << (bits - 1); + + // Iteratively multiply the longs as big as they can go. + for (long num = startingNumber; num <= n; num++) { + // Check to see if the floor(log2(num)) + 1 has changed. + if ((num & nextPowerOfTwo) != 0) { + nextPowerOfTwo <<= 1; + bits++; + } + // Get rid of the 2s in num. + int tz = Long.numberOfTrailingZeros(num); + long normalizedNum = num >> tz; + shift += tz; + // Adjust floor(log2(num)) + 1. + int normalizedBits = bits - tz; + // If it won't fit in a long, then we store off the intermediate product. + if (normalizedBits + productBits >= Long.SIZE) { + bignums.add(BigInteger.valueOf(product)); + product = 1; + productBits = 0; + } + product *= normalizedNum; + productBits = LongMath.log2(product, FLOOR) + 1; + } + // Check for leftovers. + if (product > 1) { + bignums.add(BigInteger.valueOf(product)); + } + // Efficiently multiply all the intermediate products together. + return listProduct(bignums).shiftLeft(shift); + } + + static BigInteger listProduct(List nums) { + return listProduct(nums, 0, nums.size()); + } + + static BigInteger listProduct(List nums, int start, int end) { + switch (end - start) { + case 0: + return BigInteger.ONE; + case 1: + return nums.get(start); + case 2: + return nums.get(start).multiply(nums.get(start + 1)); + case 3: + return nums.get(start).multiply(nums.get(start + 1)).multiply(nums.get(start + 2)); + default: + // Otherwise, split the list in half and recursively do this. + int m = (end + start) >>> 1; + return listProduct(nums, start, m).multiply(listProduct(nums, m, end)); + } + } + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, that is, {@code n! / (k! (n - k)!)}. + * + *

Warning: the result can take as much as O(k log n) space. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0}, or {@code k > n} + */ + public static BigInteger binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + if (k < LongMath.BIGGEST_BINOMIALS.length && n <= LongMath.BIGGEST_BINOMIALS[k]) { + return BigInteger.valueOf(LongMath.binomial(n, k)); + } + + BigInteger accum = BigInteger.ONE; + + long numeratorAccum = n; + long denominatorAccum = 1; + + int bits = LongMath.log2(n, RoundingMode.CEILING); + + int numeratorBits = bits; + + for (int i = 1; i < k; i++) { + int p = n - i; + int q = i + 1; + + // log2(p) >= bits - 1, because p >= n/2 + + if (numeratorBits + bits >= Long.SIZE - 1) { + // The numerator is as big as it can get without risking overflow. + // Multiply numeratorAccum / denominatorAccum into accum. + accum = accum + .multiply(BigInteger.valueOf(numeratorAccum)) + .divide(BigInteger.valueOf(denominatorAccum)); + numeratorAccum = p; + denominatorAccum = q; + numeratorBits = bits; + } else { + // We can definitely multiply into the long accumulators without overflowing them. + numeratorAccum *= p; + denominatorAccum *= q; + numeratorBits += bits; + } + } + return accum + .multiply(BigInteger.valueOf(numeratorAccum)) + .divide(BigInteger.valueOf(denominatorAccum)); + } + + // Returns true if BigInteger.valueOf(x.longValue()).equals(x). + @GwtIncompatible("TODO") + static boolean fitsInLong(BigInteger x) { + return x.bitLength() <= Long.SIZE - 1; + } + + private BigIntegerMath() {} +} diff --git a/guava/src/com/google/common/math/DoubleMath.java b/guava/src/com/google/common/math/DoubleMath.java new file mode 100644 index 0000000..95e8fb4 --- /dev/null +++ b/guava/src/com/google/common/math/DoubleMath.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.math.DoubleUtils.IMPLICIT_BIT; +import static com.google.common.math.DoubleUtils.SIGNIFICAND_BITS; +import static com.google.common.math.DoubleUtils.getSignificand; +import static com.google.common.math.DoubleUtils.isFinite; +import static com.google.common.math.DoubleUtils.isNormal; +import static com.google.common.math.DoubleUtils.scaleNormalize; +import static com.google.common.math.MathPreconditions.checkInRange; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.copySign; +import static java.lang.Math.getExponent; +import static java.lang.Math.log; +import static java.lang.Math.rint; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Booleans; + +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * A class for arithmetic on doubles that is not covered by {@link java.lang.Math}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +public final class DoubleMath { + /* + * This method returns a value y such that rounding y DOWN (towards zero) gives the same result + * as rounding x according to the specified mode. + */ + static double roundIntermediate(double x, RoundingMode mode) { + if (!isFinite(x)) { + throw new ArithmeticException("input is infinite or NaN"); + } + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isMathematicalInteger(x)); + return x; + + case FLOOR: + if (x >= 0.0 || isMathematicalInteger(x)) { + return x; + } else { + return x - 1.0; + } + + case CEILING: + if (x <= 0.0 || isMathematicalInteger(x)) { + return x; + } else { + return x + 1.0; + } + + case DOWN: + return x; + + case UP: + if (isMathematicalInteger(x)) { + return x; + } else { + return x + Math.copySign(1.0, x); + } + + case HALF_EVEN: + return rint(x); + + case HALF_UP: { + double z = rint(x); + if (abs(x - z) == 0.5) { + return x + copySign(0.5, x); + } else { + return z; + } + } + + case HALF_DOWN: { + double z = rint(x); + if (abs(x - z) == 0.5) { + return x; + } else { + return z; + } + } + + default: + throw new AssertionError(); + } + } + + /** + * Returns the {@code int} value that is equal to {@code x} rounded with the specified rounding + * mode, if possible. + * + * @throws ArithmeticException if + *

    + *
  • {@code x} is infinite or NaN + *
  • {@code x}, after being rounded to a mathematical integer using the specified + * rounding mode, is either less than {@code Integer.MIN_VALUE} or greater than {@code + * Integer.MAX_VALUE} + *
  • {@code x} is not a mathematical integer and {@code mode} is + * {@link RoundingMode#UNNECESSARY} + *
+ */ + public static int roundToInt(double x, RoundingMode mode) { + double z = roundIntermediate(x, mode); + checkInRange(z > MIN_INT_AS_DOUBLE - 1.0 & z < MAX_INT_AS_DOUBLE + 1.0); + return (int) z; + } + + private static final double MIN_INT_AS_DOUBLE = -0x1p31; + private static final double MAX_INT_AS_DOUBLE = 0x1p31 - 1.0; + + /** + * Returns the {@code long} value that is equal to {@code x} rounded with the specified rounding + * mode, if possible. + * + * @throws ArithmeticException if + *
    + *
  • {@code x} is infinite or NaN + *
  • {@code x}, after being rounded to a mathematical integer using the specified + * rounding mode, is either less than {@code Long.MIN_VALUE} or greater than {@code + * Long.MAX_VALUE} + *
  • {@code x} is not a mathematical integer and {@code mode} is + * {@link RoundingMode#UNNECESSARY} + *
+ */ + public static long roundToLong(double x, RoundingMode mode) { + double z = roundIntermediate(x, mode); + checkInRange(MIN_LONG_AS_DOUBLE - z < 1.0 & z < MAX_LONG_AS_DOUBLE_PLUS_ONE); + return (long) z; + } + + private static final double MIN_LONG_AS_DOUBLE = -0x1p63; + /* + * We cannot store Long.MAX_VALUE as a double without losing precision. Instead, we store + * Long.MAX_VALUE + 1 == -Long.MIN_VALUE, and then offset all comparisons by 1. + */ + private static final double MAX_LONG_AS_DOUBLE_PLUS_ONE = 0x1p63; + + /** + * Returns the {@code BigInteger} value that is equal to {@code x} rounded with the specified + * rounding mode, if possible. + * + * @throws ArithmeticException if + *
    + *
  • {@code x} is infinite or NaN + *
  • {@code x} is not a mathematical integer and {@code mode} is + * {@link RoundingMode#UNNECESSARY} + *
+ */ + public static BigInteger roundToBigInteger(double x, RoundingMode mode) { + x = roundIntermediate(x, mode); + if (MIN_LONG_AS_DOUBLE - x < 1.0 & x < MAX_LONG_AS_DOUBLE_PLUS_ONE) { + return BigInteger.valueOf((long) x); + } + int exponent = getExponent(x); + long significand = getSignificand(x); + BigInteger result = BigInteger.valueOf(significand).shiftLeft(exponent - SIGNIFICAND_BITS); + return (x < 0) ? result.negate() : result; + } + + /** + * Returns {@code true} if {@code x} is exactly equal to {@code 2^k} for some finite integer + * {@code k}. + */ + public static boolean isPowerOfTwo(double x) { + return x > 0.0 && isFinite(x) && LongMath.isPowerOfTwo(getSignificand(x)); + } + + /** + * Returns the base 2 logarithm of a double value. + * + *

Special cases: + *

    + *
  • If {@code x} is NaN or less than zero, the result is NaN. + *
  • If {@code x} is positive infinity, the result is positive infinity. + *
  • If {@code x} is positive or negative zero, the result is negative infinity. + *
+ * + *

The computed result is within 1 ulp of the exact result. + * + *

If the result of this method will be immediately rounded to an {@code int}, + * {@link #log2(double, RoundingMode)} is faster. + */ + public static double log2(double x) { + return log(x) / LN_2; // surprisingly within 1 ulp according to tests + } + + private static final double LN_2 = log(2); + + /** + * Returns the base 2 logarithm of a double value, rounded with the specified rounding mode to an + * {@code int}. + * + *

Regardless of the rounding mode, this is faster than {@code (int) log2(x)}. + * + * @throws IllegalArgumentException if {@code x <= 0.0}, {@code x} is NaN, or {@code x} is + * infinite + */ + @SuppressWarnings("fallthrough") + public static int log2(double x, RoundingMode mode) { + checkArgument(x > 0.0 && isFinite(x), "x must be positive and finite"); + int exponent = getExponent(x); + if (!isNormal(x)) { + return log2(x * IMPLICIT_BIT, mode) - SIGNIFICAND_BITS; + // Do the calculation on a normal value. + } + // x is positive, finite, and normal + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case FLOOR: + increment = false; + break; + case CEILING: + increment = !isPowerOfTwo(x); + break; + case DOWN: + increment = exponent < 0 & !isPowerOfTwo(x); + break; + case UP: + increment = exponent >= 0 & !isPowerOfTwo(x); + break; + case HALF_DOWN: + case HALF_EVEN: + case HALF_UP: + double xScaled = scaleNormalize(x); + // sqrt(2) is irrational, and the spec is relative to the "exact numerical result," + // so log2(x) is never exactly exponent + 0.5. + increment = (xScaled * xScaled) > 2.0; + break; + default: + throw new AssertionError(); + } + return increment ? exponent + 1 : exponent; + } + + /** + * Returns {@code true} if {@code x} represents a mathematical integer. + * + *

This is equivalent to, but not necessarily implemented as, the expression {@code + * !Double.isNaN(x) && !Double.isInfinite(x) && x == Math.rint(x)}. + */ + public static boolean isMathematicalInteger(double x) { + return isFinite(x) + && (x == 0.0 || + SIGNIFICAND_BITS - Long.numberOfTrailingZeros(getSignificand(x)) <= getExponent(x)); + } + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive + * integers, {@code 1} if {@code n == 0}, or e n!}, or + * {@link Double#POSITIVE_INFINITY} if {@code n! > Double.MAX_VALUE}. + * + *

The result is within 1 ulp of the true value. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static double factorial(int n) { + checkNonNegative("n", n); + if (n > MAX_FACTORIAL) { + return Double.POSITIVE_INFINITY; + } else { + // Multiplying the last (n & 0xf) values into their own accumulator gives a more accurate + // result than multiplying by EVERY_SIXTEENTH_FACTORIAL[n >> 4] directly. + double accum = 1.0; + for (int i = 1 + (n & ~0xf); i <= n; i++) { + accum *= i; + } + return accum * EVERY_SIXTEENTH_FACTORIAL[n >> 4]; + } + } + + @VisibleForTesting + static final int MAX_FACTORIAL = 170; + + @VisibleForTesting + static final double[] EVERY_SIXTEENTH_FACTORIAL = { + 0x1.0p0, + 0x1.30777758p44, + 0x1.956ad0aae33a4p117, + 0x1.ee69a78d72cb6p202, + 0x1.fe478ee34844ap295, + 0x1.c619094edabffp394, + 0x1.3638dd7bd6347p498, + 0x1.7cac197cfe503p605, + 0x1.1e5dfc140e1e5p716, + 0x1.8ce85fadb707ep829, + 0x1.95d5f3d928edep945}; + + /** + * Returns {@code true} if {@code a} and {@code b} are within {@code tolerance} of each other. + * + *

Technically speaking, this is equivalent to + * {@code Math.abs(a - b) <= tolerance || Double.valueOf(a).equals(Double.valueOf(b))}. + * + *

Notable special cases include: + *

    + *
  • All NaNs are fuzzily equal. + *
  • If {@code a == b}, then {@code a} and {@code b} are always fuzzily equal. + *
  • Positive and negative zero are always fuzzily equal. + *
  • If {@code tolerance} is zero, and neither {@code a} nor {@code b} is NaN, then + * {@code a} and {@code b} are fuzzily equal if and only if {@code a == b}. + *
  • With {@link Double#POSITIVE_INFINITY} tolerance, all non-NaN values are fuzzily equal. + *
  • With finite tolerance, {@code Double.POSITIVE_INFINITY} and {@code + * Double.NEGATIVE_INFINITY} are fuzzily equal only to themselves. + *
  • + * + *

    This is reflexive and symmetric, but not transitive, so it is not an + * equivalence relation and not suitable for use in {@link Object#equals} + * implementations. + * + * @throws IllegalArgumentException if {@code tolerance} is {@code < 0} or NaN + * @since 13.0 + */ + @Beta + public static boolean fuzzyEquals(double a, double b, double tolerance) { + MathPreconditions.checkNonNegative("tolerance", tolerance); + return + Math.copySign(a - b, 1.0) <= tolerance + // copySign(x, 1.0) is a branch-free version of abs(x), but with different NaN semantics + || (a == b) // needed to ensure that infinities equal themselves + || ((a != a) && (b != b)); // x != x is equivalent to Double.isNaN(x), but faster + } + + /** + * Compares {@code a} and {@code b} "fuzzily," with a tolerance for nearly-equal values. + * + *

    This method is equivalent to + * {@code fuzzyEquals(a, b, tolerance) ? 0 : Double.compare(a, b)}. In particular, like + * {@link Double#compare(double, double)}, it treats all NaN values as equal and greater than all + * other values (including {@link Double#POSITIVE_INFINITY}). + * + *

    This is not a total ordering and is not suitable for use in + * {@link Comparable#compareTo} implementations. In particular, it is not transitive. + * + * @throws IllegalArgumentException if {@code tolerance} is {@code < 0} or NaN + * @since 13.0 + */ + @Beta + public static int fuzzyCompare(double a, double b, double tolerance) { + if (fuzzyEquals(a, b, tolerance)) { + return 0; + } else if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return Booleans.compare(Double.isNaN(a), Double.isNaN(b)); + } + } + + private DoubleMath() {} +} diff --git a/guava/src/com/google/common/math/DoubleUtils.java b/guava/src/com/google/common/math/DoubleUtils.java new file mode 100644 index 0000000..6cd94e3 --- /dev/null +++ b/guava/src/com/google/common/math/DoubleUtils.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Double.MAX_EXPONENT; +import static java.lang.Double.MIN_EXPONENT; +import static java.lang.Double.POSITIVE_INFINITY; +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.isNaN; +import static java.lang.Double.longBitsToDouble; +import static java.lang.Math.getExponent; + +import java.math.BigInteger; + +/** + * Utilities for {@code double} primitives. + * + * @author Louis Wasserman + */ +final class DoubleUtils { + private DoubleUtils() { + } + + static double nextDown(double d) { + return -Math.nextUp(-d); + } + + // The mask for the significand, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long SIGNIFICAND_MASK = 0x000fffffffffffffL; + + // The mask for the exponent, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long EXPONENT_MASK = 0x7ff0000000000000L; + + // The mask for the sign, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long SIGN_MASK = 0x8000000000000000L; + + static final int SIGNIFICAND_BITS = 52; + + static final int EXPONENT_BIAS = 1023; + + /** + * The implicit 1 bit that is omitted in significands of normal doubles. + */ + static final long IMPLICIT_BIT = SIGNIFICAND_MASK + 1; + + static long getSignificand(double d) { + checkArgument(isFinite(d), "not a normal value"); + int exponent = getExponent(d); + long bits = doubleToRawLongBits(d); + bits &= SIGNIFICAND_MASK; + return (exponent == MIN_EXPONENT - 1) + ? bits << 1 + : bits | IMPLICIT_BIT; + } + + static boolean isFinite(double d) { + return getExponent(d) <= MAX_EXPONENT; + } + + static boolean isNormal(double d) { + return getExponent(d) >= MIN_EXPONENT; + } + + /* + * Returns x scaled by a power of 2 such that it is in the range [1, 2). Assumes x is positive, + * normal, and finite. + */ + static double scaleNormalize(double x) { + long significand = doubleToRawLongBits(x) & SIGNIFICAND_MASK; + return longBitsToDouble(significand | ONE_BITS); + } + + static double bigToDouble(BigInteger x) { + // This is an extremely fast implementation of BigInteger.doubleValue(). JDK patch pending. + BigInteger absX = x.abs(); + int exponent = absX.bitLength() - 1; + // exponent == floor(log2(abs(x))) + if (exponent < Long.SIZE - 1) { + return x.longValue(); + } else if (exponent > MAX_EXPONENT) { + return x.signum() * POSITIVE_INFINITY; + } + + /* + * We need the top SIGNIFICAND_BITS + 1 bits, including the "implicit" one bit. To make + * rounding easier, we pick out the top SIGNIFICAND_BITS + 2 bits, so we have one to help us + * round up or down. twiceSignifFloor will contain the top SIGNIFICAND_BITS + 2 bits, and + * signifFloor the top SIGNIFICAND_BITS + 1. + * + * It helps to consider the real number signif = absX * 2^(SIGNIFICAND_BITS - exponent). + */ + int shift = exponent - SIGNIFICAND_BITS - 1; + long twiceSignifFloor = absX.shiftRight(shift).longValue(); + long signifFloor = twiceSignifFloor >> 1; + signifFloor &= SIGNIFICAND_MASK; // remove the implied bit + + /* + * We round up if either the fractional part of signif is strictly greater than 0.5 (which is + * true if the 0.5 bit is set and any lower bit is set), or if the fractional part of signif is + * >= 0.5 and signifFloor is odd (which is true if both the 0.5 bit and the 1 bit are set). + */ + boolean increment = (twiceSignifFloor & 1) != 0 + && ((signifFloor & 1) != 0 || absX.getLowestSetBit() < shift); + long signifRounded = increment ? signifFloor + 1 : signifFloor; + long bits = (long) ((exponent + EXPONENT_BIAS)) << SIGNIFICAND_BITS; + bits += signifRounded; + /* + * If signifRounded == 2^53, we'd need to set all of the significand bits to zero and add 1 to + * the exponent. This is exactly the behavior we get from just adding signifRounded to bits + * directly. If the exponent is MAX_DOUBLE_EXPONENT, we round up (correctly) to + * Double.POSITIVE_INFINITY. + */ + bits |= x.signum() & SIGN_MASK; + return longBitsToDouble(bits); + } + + /** + * Returns its argument if it is non-negative, zero if it is negative. + */ + static double ensureNonNegative(double value) { + checkArgument(!isNaN(value)); + if (value > 0.0) { + return value; + } else { + return 0.0; + } + } + + private static final long ONE_BITS = doubleToRawLongBits(1.0); +} diff --git a/guava/src/com/google/common/math/IntMath.java b/guava/src/com/google/common/math/IntMath.java new file mode 100644 index 0000000..5230211 --- /dev/null +++ b/guava/src/com/google/common/math/IntMath.java @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNoOverflow; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.min; +import static java.math.RoundingMode.HALF_EVEN; +import static java.math.RoundingMode.HALF_UP; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * A class for arithmetic on values of type {@code int}. Where possible, methods are defined and + * named analogously to their {@code BigInteger} counterparts. + * + *

    The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

    Similar functionality for {@code long} and for {@link BigInteger} can be found in + * {@link LongMath} and {@link BigIntegerMath} respectively. For other common operations on + * {@code int} values, see {@link com.google.common.primitives.Ints}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class IntMath { + // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + + /** + * Returns {@code true} if {@code x} represents a power of two. + * + *

    This differs from {@code Integer.bitCount(x) == 1}, because + * {@code Integer.bitCount(Integer.MIN_VALUE) == 1}, but {@link Integer#MIN_VALUE} is not a power + * of two. + */ + public static boolean isPowerOfTwo(int x) { + return x > 0 & (x & (x - 1)) == 0; + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + public static int log2(int x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Integer.numberOfLeadingZeros(x); + int cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Integer.SIZE - 1) - leadingZeros; + return (x <= cmp) ? logFloor : logFloor + 1; + + default: + throw new AssertionError(); + } + } + + /** The biggest half power of two that can fit in an unsigned int. */ + @VisibleForTesting static final int MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333; + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible("need BigIntegerMath to adequately test") + @SuppressWarnings("fallthrough") + public static int log10(int x, RoundingMode mode) { + checkPositive("x", x); + int logFloor = log10Floor(x); + int floorPow = POWERS_OF_10[logFloor]; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(x == floorPow); + // fall through + case FLOOR: + case DOWN: + return logFloor; + case CEILING: + case UP: + return (x == floorPow) ? logFloor : logFloor + 1; + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // sqrt(10) is irrational, so log10(x) - logFloor is never exactly 0.5 + return (x <= HALF_POWERS_OF_10[logFloor]) ? logFloor : logFloor + 1; + default: + throw new AssertionError(); + } + } + + private static int log10Floor(int x) { + /* + * Based on Hacker's Delight Fig. 11-5, the two-table-lookup, branch-free implementation. + * + * The key idea is that based on the number of leading zeros (equivalently, floor(log2(x))), + * we can narrow the possible floor(log10(x)) values to two. For example, if floor(log2(x)) + * is 6, then 64 <= x < 128, so floor(log10(x)) is either 1 or 2. + */ + int y = MAX_LOG10_FOR_LEADING_ZEROS[Integer.numberOfLeadingZeros(x)]; + // y is the higher of the two possible values of floor(log10(x)) + + int sgn = (x - POWERS_OF_10[y]) >>> (Integer.SIZE - 1); + /* + * sgn is the sign bit of x - 10^y; it is 1 if x < 10^y, and 0 otherwise. If x < 10^y, then we + * want the lower of the two possible values, or y - 1, otherwise, we want y. + */ + return y - sgn; + } + + // MAX_LOG10_FOR_LEADING_ZEROS[i] == floor(log10(2^(Long.SIZE - i))) + @VisibleForTesting static final byte[] MAX_LOG10_FOR_LEADING_ZEROS = {9, 9, 9, 8, 8, 8, + 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0}; + + @VisibleForTesting static final int[] POWERS_OF_10 = {1, 10, 100, 1000, 10000, + 100000, 1000000, 10000000, 100000000, 1000000000}; + + // HALF_POWERS_OF_10[i] = largest int less than 10^(i + 0.5) + @VisibleForTesting static final int[] HALF_POWERS_OF_10 = + {3, 31, 316, 3162, 31622, 316227, 3162277, 31622776, 316227766, Integer.MAX_VALUE}; + + /** + * Returns {@code b} to the {@code k}th power. Even if the result overflows, it will be equal to + * {@code BigInteger.valueOf(b).pow(k).intValue()}. This implementation runs in {@code O(log k)} + * time. + * + *

    Compare {@link #checkedPow}, which throws an {@link ArithmeticException} upon overflow. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @GwtIncompatible("failing tests") + public static int pow(int b, int k) { + checkNonNegative("exponent", k); + switch (b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + return (k < Integer.SIZE) ? (1 << k) : 0; + case (-2): + if (k < Integer.SIZE) { + return ((k & 1) == 0) ? (1 << k) : -(1 << k); + } else { + return 0; + } + } + for (int accum = 1;; k >>= 1) { + switch (k) { + case 0: + return accum; + case 1: + return b * accum; + default: + accum *= ((k & 1) == 0) ? 1 : b; + b *= b; + } + } + } + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and + * {@code sqrt(x)} is not an integer + */ + @GwtIncompatible("need BigIntegerMath to adequately test") + @SuppressWarnings("fallthrough") + public static int sqrt(int x, RoundingMode mode) { + checkNonNegative("x", x); + int sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor * sqrtFloor == x); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + return (sqrtFloor * sqrtFloor == x) ? sqrtFloor : sqrtFloor + 1; + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + int halfSquare = sqrtFloor * sqrtFloor + sqrtFloor; + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. + * Since both x and halfSquare are integers, this is equivalent to testing whether or not + * x <= halfSquare. (We have to deal with overflow, though.) + */ + return (x <= halfSquare | halfSquare < 0) ? sqrtFloor : sqrtFloor + 1; + default: + throw new AssertionError(); + } + } + + private static int sqrtFloor(int x) { + // There is no loss of precision in converting an int to a double, according to + // http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.1.2 + return (int) Math.sqrt(x); + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified + * {@code RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @SuppressWarnings("fallthrough") + public static int divide(int p, int q, RoundingMode mode) { + checkNotNull(mode); + if (q == 0) { + throw new ArithmeticException("/ by zero"); // for GWT + } + int div = p / q; + int rem = p - q * div; // equal to p % q + + if (rem == 0) { + return div; + } + + /* + * Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to + * deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of + * p / q. + * + * signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise. + */ + int signum = 1 | ((p ^ q) >> (Integer.SIZE - 1)); + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(rem == 0); + // fall through + case DOWN: + increment = false; + break; + case UP: + increment = true; + break; + case CEILING: + increment = signum > 0; + break; + case FLOOR: + increment = signum < 0; + break; + case HALF_EVEN: + case HALF_DOWN: + case HALF_UP: + int absRem = abs(rem); + int cmpRemToHalfDivisor = absRem - (abs(q) - absRem); + // subtracting two nonnegative ints can't overflow + // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). + if (cmpRemToHalfDivisor == 0) { // exactly on the half mark + increment = (mode == HALF_UP || (mode == HALF_EVEN & (div & 1) != 0)); + } else { + increment = cmpRemToHalfDivisor > 0; // closer to the UP value + } + break; + default: + throw new AssertionError(); + } + return increment ? div + signum : div; + } + + /** + * Returns {@code x mod m}. This differs from {@code x % m} in that it always returns a + * non-negative result. + * + *

    For example:

     {@code
    +   *
    +   * mod(7, 4) == 3
    +   * mod(-7, 4) == 1
    +   * mod(-1, 4) == 3
    +   * mod(-8, 4) == 0
    +   * mod(8, 4) == 0}
    + * + * @throws ArithmeticException if {@code m <= 0} + */ + public static int mod(int x, int m) { + if (m <= 0) { + throw new ArithmeticException("Modulus " + m + " must be > 0"); + } + int result = x % m; + return (result >= 0) ? result : result + m; + } + + /** + * Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if + * {@code a == 0 && b == 0}. + * + * @throws IllegalArgumentException if {@code a < 0} or {@code b < 0} + */ + public static int gcd(int a, int b) { + /* + * The reason we require both arguments to be >= 0 is because otherwise, what do you return on + * gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive 2^31 + * isn't an int. + */ + checkNonNegative("a", a); + checkNonNegative("b", b); + if (a == 0) { + // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b; + } else if (b == 0) { + return a; // similar logic + } + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. + * This is >40% faster than the Euclidean algorithm in benchmarks. + */ + int aTwos = Integer.numberOfTrailingZeros(a); + a >>= aTwos; // divide out all 2s + int bTwos = Integer.numberOfTrailingZeros(b); + b >>= bTwos; // divide out all 2s + while (a != b) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). + // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. + + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + + int delta = a - b; // can't overflow, since a and b are nonnegative + + int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1)); + // equivalent to Math.min(delta, 0) + + a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) + // a is now nonnegative and even + + b += minDeltaOrZero; // sets b to min(old a, b) + a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b + } + return a << min(aTwos, bTwos); + } + + /** + * Returns the sum of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a + b} overflows in signed {@code int} arithmetic + */ + public static int checkedAdd(int a, int b) { + long result = (long) a + b; + checkNoOverflow(result == (int) result); + return (int) result; + } + + /** + * Returns the difference of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a - b} overflows in signed {@code int} arithmetic + */ + public static int checkedSubtract(int a, int b) { + long result = (long) a - b; + checkNoOverflow(result == (int) result); + return (int) result; + } + + /** + * Returns the product of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a * b} overflows in signed {@code int} arithmetic + */ + public static int checkedMultiply(int a, int b) { + long result = (long) a * b; + checkNoOverflow(result == (int) result); + return (int) result; + } + + /** + * Returns the {@code b} to the {@code k}th power, provided it does not overflow. + * + *

    {@link #pow} may be faster, but does not check for overflow. + * + * @throws ArithmeticException if {@code b} to the {@code k}th power overflows in signed + * {@code int} arithmetic + */ + public static int checkedPow(int b, int k) { + checkNonNegative("exponent", k); + switch (b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + checkNoOverflow(k < Integer.SIZE - 1); + return 1 << k; + case (-2): + checkNoOverflow(k < Integer.SIZE); + return ((k & 1) == 0) ? 1 << k : -1 << k; + } + int accum = 1; + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return checkedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = checkedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + checkNoOverflow(-FLOOR_SQRT_MAX_INT <= b & b <= FLOOR_SQRT_MAX_INT); + b *= b; + } + } + } + } + + @VisibleForTesting static final int FLOOR_SQRT_MAX_INT = 46340; + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive + * integers, {@code 1} if {@code n == 0}, or {@link Integer#MAX_VALUE} if the + * result does not fit in a {@code int}. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static int factorial(int n) { + checkNonNegative("n", n); + return (n < FACTORIALS.length) ? FACTORIALS[n] : Integer.MAX_VALUE; + } + + static final int[] FACTORIALS = { + 1, + 1, + 1 * 2, + 1 * 2 * 3, + 1 * 2 * 3 * 4, + 1 * 2 * 3 * 4 * 5, + 1 * 2 * 3 * 4 * 5 * 6, + 1 * 2 * 3 * 4 * 5 * 6 * 7, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12}; + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, or {@link Integer#MAX_VALUE} if the result does not fit in an {@code int}. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0} or {@code k > n} + */ + @GwtIncompatible("need BigIntegerMath to adequately test") + public static int binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + if (k >= BIGGEST_BINOMIALS.length || n > BIGGEST_BINOMIALS[k]) { + return Integer.MAX_VALUE; + } + switch (k) { + case 0: + return 1; + case 1: + return n; + default: + long result = 1; + for (int i = 0; i < k; i++) { + result *= n - i; + result /= i + 1; + } + return (int) result; + } + } + + // binomial(BIGGEST_BINOMIALS[k], k) fits in an int, but not binomial(BIGGEST_BINOMIALS[k]+1,k). + @VisibleForTesting static int[] BIGGEST_BINOMIALS = { + Integer.MAX_VALUE, + Integer.MAX_VALUE, + 65536, + 2345, + 477, + 193, + 110, + 75, + 58, + 49, + 43, + 39, + 37, + 35, + 34, + 34, + 33 + }; + + private IntMath() {} +} diff --git a/guava/src/com/google/common/math/LongMath.java b/guava/src/com/google/common/math/LongMath.java new file mode 100644 index 0000000..8334097 --- /dev/null +++ b/guava/src/com/google/common/math/LongMath.java @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNoOverflow; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.min; +import static java.math.RoundingMode.HALF_EVEN; +import static java.math.RoundingMode.HALF_UP; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * A class for arithmetic on values of type {@code long}. Where possible, methods are defined and + * named analogously to their {@code BigInteger} counterparts. + * + *

    The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

    Similar functionality for {@code int} and for {@link BigInteger} can be found in + * {@link IntMath} and {@link BigIntegerMath} respectively. For other common operations on + * {@code long} values, see {@link com.google.common.primitives.Longs}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class LongMath { + // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + + /** + * Returns {@code true} if {@code x} represents a power of two. + * + *

    This differs from {@code Long.bitCount(x) == 1}, because + * {@code Long.bitCount(Long.MIN_VALUE) == 1}, but {@link Long#MIN_VALUE} is not a power of two. + */ + public static boolean isPowerOfTwo(long x) { + return x > 0 & (x & (x - 1)) == 0; + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + public static int log2(long x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Long.SIZE - 1) - Long.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Long.SIZE - Long.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Long.numberOfLeadingZeros(x); + long cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Long.SIZE - 1) - leadingZeros; + return (x <= cmp) ? logFloor : logFloor + 1; + + default: + throw new AssertionError("impossible"); + } + } + + /** The biggest half power of two that fits into an unsigned long */ + @VisibleForTesting static final long MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333F9DE6484L; + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible("TODO") + @SuppressWarnings("fallthrough") + public static int log10(long x, RoundingMode mode) { + checkPositive("x", x); + if (fitsInInt(x)) { + return IntMath.log10((int) x, mode); + } + int logFloor = log10Floor(x); + long floorPow = POWERS_OF_10[logFloor]; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(x == floorPow); + // fall through + case FLOOR: + case DOWN: + return logFloor; + case CEILING: + case UP: + return (x == floorPow) ? logFloor : logFloor + 1; + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // sqrt(10) is irrational, so log10(x)-logFloor is never exactly 0.5 + return (x <= HALF_POWERS_OF_10[logFloor]) ? logFloor : logFloor + 1; + default: + throw new AssertionError(); + } + } + + @GwtIncompatible("TODO") + static int log10Floor(long x) { + /* + * Based on Hacker's Delight Fig. 11-5, the two-table-lookup, branch-free implementation. + * + * The key idea is that based on the number of leading zeros (equivalently, floor(log2(x))), + * we can narrow the possible floor(log10(x)) values to two. For example, if floor(log2(x)) + * is 6, then 64 <= x < 128, so floor(log10(x)) is either 1 or 2. + */ + int y = MAX_LOG10_FOR_LEADING_ZEROS[Long.numberOfLeadingZeros(x)]; + // y is the higher of the two possible values of floor(log10(x)) + + long sgn = (x - POWERS_OF_10[y]) >>> (Long.SIZE - 1); + /* + * sgn is the sign bit of x - 10^y; it is 1 if x < 10^y, and 0 otherwise. If x < 10^y, then we + * want the lower of the two possible values, or y - 1, otherwise, we want y. + */ + return y - (int) sgn; + } + + // MAX_LOG10_FOR_LEADING_ZEROS[i] == floor(log10(2^(Long.SIZE - i))) + @VisibleForTesting static final byte[] MAX_LOG10_FOR_LEADING_ZEROS = { + 19, 18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, + 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, + 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0 }; + + @GwtIncompatible("TODO") + @VisibleForTesting + static final long[] POWERS_OF_10 = { + 1L, + 10L, + 100L, + 1000L, + 10000L, + 100000L, + 1000000L, + 10000000L, + 100000000L, + 1000000000L, + 10000000000L, + 100000000000L, + 1000000000000L, + 10000000000000L, + 100000000000000L, + 1000000000000000L, + 10000000000000000L, + 100000000000000000L, + 1000000000000000000L + }; + + // HALF_POWERS_OF_10[i] = largest long less than 10^(i + 0.5) + @GwtIncompatible("TODO") + @VisibleForTesting + static final long[] HALF_POWERS_OF_10 = { + 3L, + 31L, + 316L, + 3162L, + 31622L, + 316227L, + 3162277L, + 31622776L, + 316227766L, + 3162277660L, + 31622776601L, + 316227766016L, + 3162277660168L, + 31622776601683L, + 316227766016837L, + 3162277660168379L, + 31622776601683793L, + 316227766016837933L, + 3162277660168379331L + }; + + /** + * Returns {@code b} to the {@code k}th power. Even if the result overflows, it will be equal to + * {@code BigInteger.valueOf(b).pow(k).longValue()}. This implementation runs in {@code O(log k)} + * time. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @GwtIncompatible("TODO") + public static long pow(long b, int k) { + checkNonNegative("exponent", k); + if (-2 <= b && b <= 2) { + switch ((int) b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + return (k < Long.SIZE) ? 1L << k : 0; + case (-2): + if (k < Long.SIZE) { + return ((k & 1) == 0) ? 1L << k : -(1L << k); + } else { + return 0; + } + } + } + for (long accum = 1;; k >>= 1) { + switch (k) { + case 0: + return accum; + case 1: + return accum * b; + default: + accum *= ((k & 1) == 0) ? 1 : b; + b *= b; + } + } + } + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and + * {@code sqrt(x)} is not an integer + */ + @GwtIncompatible("TODO") + @SuppressWarnings("fallthrough") + public static long sqrt(long x, RoundingMode mode) { + checkNonNegative("x", x); + if (fitsInInt(x)) { + return IntMath.sqrt((int) x, mode); + } + long sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor * sqrtFloor == x); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + return (sqrtFloor * sqrtFloor == x) ? sqrtFloor : sqrtFloor + 1; + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + long halfSquare = sqrtFloor * sqrtFloor + sqrtFloor; + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both + * x and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. (We have to deal with overflow, though.) + */ + return (halfSquare >= x | halfSquare < 0) ? sqrtFloor : sqrtFloor + 1; + default: + throw new AssertionError(); + } + } + + @GwtIncompatible("TODO") + private static long sqrtFloor(long x) { + // Hackers's Delight, Figure 11-1 + long sqrt0 = (long) Math.sqrt(x); + // Precision can be lost in the cast to double, so we use this as a starting estimate. + long sqrt1 = (sqrt0 + (x / sqrt0)) >> 1; + if (sqrt1 == sqrt0) { + return sqrt0; + } + do { + sqrt0 = sqrt1; + sqrt1 = (sqrt0 + (x / sqrt0)) >> 1; + } while (sqrt1 < sqrt0); + return sqrt0; + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified + * {@code RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @GwtIncompatible("TODO") + @SuppressWarnings("fallthrough") + public static long divide(long p, long q, RoundingMode mode) { + checkNotNull(mode); + long div = p / q; // throws if q == 0 + long rem = p - q * div; // equals p % q + + if (rem == 0) { + return div; + } + + /* + * Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to + * deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of + * p / q. + * + * signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise. + */ + int signum = 1 | (int) ((p ^ q) >> (Long.SIZE - 1)); + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(rem == 0); + // fall through + case DOWN: + increment = false; + break; + case UP: + increment = true; + break; + case CEILING: + increment = signum > 0; + break; + case FLOOR: + increment = signum < 0; + break; + case HALF_EVEN: + case HALF_DOWN: + case HALF_UP: + long absRem = abs(rem); + long cmpRemToHalfDivisor = absRem - (abs(q) - absRem); + // subtracting two nonnegative longs can't overflow + // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). + if (cmpRemToHalfDivisor == 0) { // exactly on the half mark + increment = (mode == HALF_UP | (mode == HALF_EVEN & (div & 1) != 0)); + } else { + increment = cmpRemToHalfDivisor > 0; // closer to the UP value + } + break; + default: + throw new AssertionError(); + } + return increment ? div + signum : div; + } + + /** + * Returns {@code x mod m}. This differs from {@code x % m} in that it always returns a + * non-negative result. + * + *

    For example: + * + *

     {@code
    +   *
    +   * mod(7, 4) == 3
    +   * mod(-7, 4) == 1
    +   * mod(-1, 4) == 3
    +   * mod(-8, 4) == 0
    +   * mod(8, 4) == 0}
    + * + * @throws ArithmeticException if {@code m <= 0} + */ + @GwtIncompatible("TODO") + public static int mod(long x, int m) { + // Cast is safe because the result is guaranteed in the range [0, m) + return (int) mod(x, (long) m); + } + + /** + * Returns {@code x mod m}. This differs from {@code x % m} in that it always returns a + * non-negative result. + * + *

    For example: + * + *

     {@code
    +   *
    +   * mod(7, 4) == 3
    +   * mod(-7, 4) == 1
    +   * mod(-1, 4) == 3
    +   * mod(-8, 4) == 0
    +   * mod(8, 4) == 0}
    + * + * @throws ArithmeticException if {@code m <= 0} + */ + @GwtIncompatible("TODO") + public static long mod(long x, long m) { + if (m <= 0) { + throw new ArithmeticException("Modulus " + m + " must be > 0"); + } + long result = x % m; + return (result >= 0) ? result : result + m; + } + + /** + * Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if + * {@code a == 0 && b == 0}. + * + * @throws IllegalArgumentException if {@code a < 0} or {@code b < 0} + */ + @GwtIncompatible("TODO") + public static long gcd(long a, long b) { + /* + * The reason we require both arguments to be >= 0 is because otherwise, what do you return on + * gcd(0, Long.MIN_VALUE)? BigInteger.gcd would return positive 2^63, but positive 2^63 isn't + * an int. + */ + checkNonNegative("a", a); + checkNonNegative("b", b); + if (a == 0) { + // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b; + } else if (b == 0) { + return a; // similar logic + } + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. + * This is >60% faster than the Euclidean algorithm in benchmarks. + */ + int aTwos = Long.numberOfTrailingZeros(a); + a >>= aTwos; // divide out all 2s + int bTwos = Long.numberOfTrailingZeros(b); + b >>= bTwos; // divide out all 2s + while (a != b) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). + // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. + + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + + long delta = a - b; // can't overflow, since a and b are nonnegative + + long minDeltaOrZero = delta & (delta >> (Long.SIZE - 1)); + // equivalent to Math.min(delta, 0) + + a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) + // a is now nonnegative and even + + b += minDeltaOrZero; // sets b to min(old a, b) + a >>= Long.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b + } + return a << min(aTwos, bTwos); + } + + /** + * Returns the sum of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a + b} overflows in signed {@code long} arithmetic + */ + @GwtIncompatible("TODO") + public static long checkedAdd(long a, long b) { + long result = a + b; + checkNoOverflow((a ^ b) < 0 | (a ^ result) >= 0); + return result; + } + + /** + * Returns the difference of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a - b} overflows in signed {@code long} arithmetic + */ + @GwtIncompatible("TODO") + public static long checkedSubtract(long a, long b) { + long result = a - b; + checkNoOverflow((a ^ b) >= 0 | (a ^ result) >= 0); + return result; + } + + /** + * Returns the product of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a * b} overflows in signed {@code long} arithmetic + */ + @GwtIncompatible("TODO") + public static long checkedMultiply(long a, long b) { + // Hacker's Delight, Section 2-12 + int leadingZeros = Long.numberOfLeadingZeros(a) + Long.numberOfLeadingZeros(~a) + + Long.numberOfLeadingZeros(b) + Long.numberOfLeadingZeros(~b); + /* + * If leadingZeros > Long.SIZE + 1 it's definitely fine, if it's < Long.SIZE it's definitely + * bad. We do the leadingZeros check to avoid the division below if at all possible. + * + * Otherwise, if b == Long.MIN_VALUE, then the only allowed values of a are 0 and 1. We take + * care of all a < 0 with their own check, because in particular, the case a == -1 will + * incorrectly pass the division check below. + * + * In all other cases, we check that either a is 0 or the result is consistent with division. + */ + if (leadingZeros > Long.SIZE + 1) { + return a * b; + } + checkNoOverflow(leadingZeros >= Long.SIZE); + checkNoOverflow(a >= 0 | b != Long.MIN_VALUE); + long result = a * b; + checkNoOverflow(a == 0 || result / a == b); + return result; + } + + /** + * Returns the {@code b} to the {@code k}th power, provided it does not overflow. + * + * @throws ArithmeticException if {@code b} to the {@code k}th power overflows in signed + * {@code long} arithmetic + */ + @GwtIncompatible("TODO") + public static long checkedPow(long b, int k) { + checkNonNegative("exponent", k); + if (b >= -2 & b <= 2) { + switch ((int) b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + checkNoOverflow(k < Long.SIZE - 1); + return 1L << k; + case (-2): + checkNoOverflow(k < Long.SIZE); + return ((k & 1) == 0) ? (1L << k) : (-1L << k); + } + } + long accum = 1; + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return checkedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = checkedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + checkNoOverflow(b <= FLOOR_SQRT_MAX_LONG); + b *= b; + } + } + } + } + + @GwtIncompatible("TODO") + @VisibleForTesting static final long FLOOR_SQRT_MAX_LONG = 3037000499L; + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive + * integers, {@code 1} if {@code n == 0}, or {@link Long#MAX_VALUE} if the + * result does not fit in a {@code long}. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + @GwtIncompatible("TODO") + public static long factorial(int n) { + checkNonNegative("n", n); + return (n < FACTORIALS.length) ? FACTORIALS[n] : Long.MAX_VALUE; + } + + static final long[] FACTORIALS = { + 1L, + 1L, + 1L * 2, + 1L * 2 * 3, + 1L * 2 * 3 * 4, + 1L * 2 * 3 * 4 * 5, + 1L * 2 * 3 * 4 * 5 * 6, + 1L * 2 * 3 * 4 * 5 * 6 * 7, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 + }; + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, or {@link Long#MAX_VALUE} if the result does not fit in a {@code long}. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0}, or {@code k > n} + */ + public static long binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + if (k >= BIGGEST_BINOMIALS.length || n > BIGGEST_BINOMIALS[k]) { + return Long.MAX_VALUE; + } + long result = 1; + if (k < BIGGEST_SIMPLE_BINOMIALS.length && n <= BIGGEST_SIMPLE_BINOMIALS[k]) { + // guaranteed not to overflow + for (int i = 0; i < k; i++) { + result *= n - i; + result /= i + 1; + } + } else { + // We want to do this in long math for speed, but want to avoid overflow. + // Dividing by the GCD suffices to avoid overflow in all the remaining cases. + for (int i = 1; i <= k; i++, n--) { + int d = IntMath.gcd(n, i); + result /= i / d; // (i/d) is guaranteed to divide result + result *= n / d; + } + } + return result; + } + + /* + * binomial(BIGGEST_BINOMIALS[k], k) fits in a long, but not + * binomial(BIGGEST_BINOMIALS[k] + 1, k). + */ + static final int[] BIGGEST_BINOMIALS = + {Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 3810779, 121977, 16175, 4337, 1733, + 887, 534, 361, 265, 206, 169, 143, 125, 111, 101, 94, 88, 83, 79, 76, 74, 72, 70, 69, 68, + 67, 67, 66, 66, 66, 66}; + + /* + * binomial(BIGGEST_SIMPLE_BINOMIALS[k], k) doesn't need to use the slower GCD-based impl, + * but binomial(BIGGEST_SIMPLE_BINOMIALS[k] + 1, k) does. + */ + @VisibleForTesting static final int[] BIGGEST_SIMPLE_BINOMIALS = + {Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 2642246, 86251, 11724, 3218, 1313, + 684, 419, 287, 214, 169, 139, 119, 105, 95, 87, 81, 76, 73, 70, 68, 66, 64, 63, 62, 62, + 61, 61, 61}; + // These values were generated by using checkedMultiply to see when the simple multiply/divide + // algorithm would lead to an overflow. + + @GwtIncompatible("TODO") + static boolean fitsInInt(long x) { + return (int) x == x; + } + + private LongMath() {} +} diff --git a/guava/src/com/google/common/math/MathPreconditions.java b/guava/src/com/google/common/math/MathPreconditions.java new file mode 100644 index 0000000..96835c6 --- /dev/null +++ b/guava/src/com/google/common/math/MathPreconditions.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations + * under the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.math.BigInteger; + +/** + * A collection of preconditions for math functions. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class MathPreconditions { + static int checkPositive(String role, int x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static long checkPositive(String role, long x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static BigInteger checkPositive(String role, BigInteger x) { + if (x.signum() <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static int checkNonNegative(String role, int x) { + if (x < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static long checkNonNegative(String role, long x) { + if (x < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static BigInteger checkNonNegative(String role, BigInteger x) { + if (checkNotNull(x).signum() < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static double checkNonNegative(String role, double x) { + if (!(x >= 0)) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static void checkRoundingUnnecessary(boolean condition) { + if (!condition) { + throw new ArithmeticException("mode was UNNECESSARY, but rounding was necessary"); + } + } + + static void checkInRange(boolean condition) { + if (!condition) { + throw new ArithmeticException("not in range"); + } + } + + static void checkNoOverflow(boolean condition) { + if (!condition) { + throw new ArithmeticException("overflow"); + } + } + + private MathPreconditions() {} +} diff --git a/guava/src/com/google/common/math/package-info.java b/guava/src/com/google/common/math/package-info.java new file mode 100644 index 0000000..bd17e52 --- /dev/null +++ b/guava/src/com/google/common/math/package-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Arithmetic functions operating on primitive values and {@link java.math.BigInteger} instances. + * + *

    This package is a part of the open-source + * Guava libraries. + * + *

    See the Guava User Guide article on + * math utilities. + */ +@ParametersAreNonnullByDefault +package com.google.common.math; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/guava/src/com/google/common/net/HostAndPort.java b/guava/src/com/google/common/net/HostAndPort.java new file mode 100644 index 0000000..76ede3f --- /dev/null +++ b/guava/src/com/google/common/net/HostAndPort.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.concurrent.Immutable; + +/** + * An immutable representation of a host and port. + * + *

    Example usage: + *

    + * HostAndPort hp = HostAndPort.fromString("[2001:db8::1]")
    + *     .withDefaultPort(80)
    + *     .requireBracketsForIPv6();
    + * hp.getHostText();  // returns "2001:db8::1"
    + * hp.getPort();      // returns 80
    + * hp.toString();     // returns "[2001:db8::1]:80"
    + * 
    + * + *

    Here are some examples of recognized formats: + *

      + *
    • example.com + *
    • example.com:80 + *
    • 192.0.2.1 + *
    • 192.0.2.1:80 + *
    • [2001:db8::1] - {@link #getHostText()} omits brackets + *
    • [2001:db8::1]:80 - {@link #getHostText()} omits brackets + *
    • 2001:db8::1 - Use {@link #requireBracketsForIPv6()} to prohibit this + *
    + * + *

    Note that this is not an exhaustive list, because these methods are only + * concerned with brackets, colons, and port numbers. Full validation of the + * host field (if desired) is the caller's responsibility. + * + * @author Paul Marks + * @since 10.0 + */ +@Beta @Immutable +public final class HostAndPort implements Serializable { + /** Magic value indicating the absence of a port number. */ + private static final int NO_PORT = -1; + + /** Hostname, IPv4/IPv6 literal, or unvalidated nonsense. */ + private final String host; + + /** Validated port number in the range [0..65535], or NO_PORT */ + private final int port; + + /** True if the parsed host has colons, but no surrounding brackets. */ + private final boolean hasBracketlessColons; + + private HostAndPort(String host, int port, boolean hasBracketlessColons) { + this.host = host; + this.port = port; + this.hasBracketlessColons = hasBracketlessColons; + } + + /** + * Returns the portion of this {@code HostAndPort} instance that should + * represent the hostname or IPv4/IPv6 literal. + * + * A successful parse does not imply any degree of sanity in this field. + * For additional validation, see the {@link HostSpecifier} class. + */ + public String getHostText() { + return host; + } + + /** Return true if this instance has a defined port. */ + public boolean hasPort() { + return port >= 0; + } + + /** + * Get the current port number, failing if no port is defined. + * + * @return a validated port number, in the range [0..65535] + * @throws IllegalStateException if no port is defined. You can use + * {@link #withDefaultPort(int)} to prevent this from occurring. + */ + public int getPort() { + checkState(hasPort()); + return port; + } + + /** + * Returns the current port number, with a default if no port is defined. + */ + public int getPortOrDefault(int defaultPort) { + return hasPort() ? port : defaultPort; + } + + /** + * Build a HostAndPort instance from separate host and port values. + * + *

    Note: Non-bracketed IPv6 literals are allowed. + * Use {@link #requireBracketsForIPv6()} to prohibit these. + * + * @param host the host string to parse. Must not contain a port number. + * @param port a port number from [0..65535] + * @return if parsing was successful, a populated HostAndPort object. + * @throws IllegalArgumentException if {@code host} contains a port number, + * or {@code port} is out of range. + */ + public static HostAndPort fromParts(String host, int port) { + checkArgument(isValidPort(port)); + HostAndPort parsedHost = fromString(host); + checkArgument(!parsedHost.hasPort()); + return new HostAndPort(parsedHost.host, port, parsedHost.hasBracketlessColons); + } + + private static final Pattern BRACKET_PATTERN = Pattern.compile("^\\[(.*:.*)\\](?::(\\d*))?$"); + + /** + * Split a freeform string into a host and port, without strict validation. + * + * Note that the host-only formats will leave the port field undefined. You + * can use {@link #withDefaultPort(int)} to patch in a default value. + * + * @param hostPortString the input string to parse. + * @return if parsing was successful, a populated HostAndPort object. + * @throws IllegalArgumentException if nothing meaningful could be parsed. + */ + public static HostAndPort fromString(String hostPortString) { + checkNotNull(hostPortString); + String host; + String portString = null; + boolean hasBracketlessColons = false; + + if (hostPortString.startsWith("[")) { + // Parse a bracketed host, typically an IPv6 literal. + Matcher matcher = BRACKET_PATTERN.matcher(hostPortString); + checkArgument(matcher.matches(), "Invalid bracketed host/port: %s", hostPortString); + host = matcher.group(1); + portString = matcher.group(2); // could be null + } else { + int colonPos = hostPortString.indexOf(':'); + if (colonPos >= 0 && hostPortString.indexOf(':', colonPos + 1) == -1) { + // Exactly 1 colon. Split into host:port. + host = hostPortString.substring(0, colonPos); + portString = hostPortString.substring(colonPos + 1); + } else { + // 0 or 2+ colons. Bare hostname or IPv6 literal. + host = hostPortString; + hasBracketlessColons = (colonPos >= 0); + } + } + + int port = NO_PORT; + if (portString != null) { + // Try to parse the whole port string as a number. + // JDK7 accepts leading plus signs. We don't want to. + checkArgument(!portString.startsWith("+"), "Unparseable port number: %s", hostPortString); + try { + port = Integer.parseInt(portString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Unparseable port number: " + hostPortString); + } + checkArgument(isValidPort(port), "Port number out of range: %s", hostPortString); + } + + return new HostAndPort(host, port, hasBracketlessColons); + } + + /** + * Provide a default port if the parsed string contained only a host. + * + * You can chain this after {@link #fromString(String)} to include a port in + * case the port was omitted from the input string. If a port was already + * provided, then this method is a no-op. + * + * @param defaultPort a port number, from [0..65535] + * @return a HostAndPort instance, guaranteed to have a defined port. + */ + public HostAndPort withDefaultPort(int defaultPort) { + checkArgument(isValidPort(defaultPort)); + if (hasPort() || port == defaultPort) { + return this; + } + return new HostAndPort(host, defaultPort, hasBracketlessColons); + } + + /** + * Generate an error if the host might be a non-bracketed IPv6 literal. + * + *

    URI formatting requires that IPv6 literals be surrounded by brackets, + * like "[2001:db8::1]". Chain this call after {@link #fromString(String)} + * to increase the strictness of the parser, and disallow IPv6 literals + * that don't contain these brackets. + * + *

    Note that this parser identifies IPv6 literals solely based on the + * presence of a colon. To perform actual validation of IP addresses, see + * the {@link InetAddresses#forString(String)} method. + * + * @return {@code this}, to enable chaining of calls. + * @throws IllegalArgumentException if bracketless IPv6 is detected. + */ + public HostAndPort requireBracketsForIPv6() { + checkArgument(!hasBracketlessColons, "Possible bracketless IPv6 literal: %s", host); + return this; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof HostAndPort) { + HostAndPort that = (HostAndPort) other; + return Objects.equal(this.host, that.host) + && this.port == that.port + && this.hasBracketlessColons == that.hasBracketlessColons; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(host, port, hasBracketlessColons); + } + + /** Rebuild the host:port string, including brackets if necessary. */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(host.length() + 7); + if (host.indexOf(':') >= 0) { + builder.append('[').append(host).append(']'); + } else { + builder.append(host); + } + if (hasPort()) { + builder.append(':').append(port); + } + return builder.toString(); + } + + /** Return true for valid port numbers. */ + private static boolean isValidPort(int port) { + return port >= 0 && port <= 65535; + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/net/HostSpecifier.java b/guava/src/com/google/common/net/HostSpecifier.java new file mode 100644 index 0000000..3c90985 --- /dev/null +++ b/guava/src/com/google/common/net/HostSpecifier.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.net.InetAddress; +import java.text.ParseException; + +import javax.annotation.Nullable; + +/** + * A syntactically valid host specifier, suitable for use in a URI. + * This may be either a numeric IP address in IPv4 or IPv6 notation, or a + * domain name. + * + *

    Because this class is intended to represent host specifiers which can + * reasonably be used in a URI, the domain name case is further restricted to + * include only those domain names which end in a recognized public suffix; see + * {@link InternetDomainName#isPublicSuffix()} for details. + * + *

    Note that no network lookups are performed by any {@code HostSpecifier} + * methods. No attempt is made to verify that a provided specifier corresponds + * to a real or accessible host. Only syntactic and pattern-based checks are + * performed. + * + *

    If you know that a given string represents a numeric IP address, use + * {@link InetAddresses} to obtain and manipulate a + * {@link java.net.InetAddress} instance from it rather than using this class. + * Similarly, if you know that a given string represents a domain name, use + * {@link InternetDomainName} rather than this class. + * + * @author Craig Berry + * @since 5.0 + */ +@Beta +public final class HostSpecifier { + + private final String canonicalForm; + + private HostSpecifier(String canonicalForm) { + this.canonicalForm = canonicalForm; + } + + /** + * Returns a {@code HostSpecifier} built from the provided {@code specifier}, + * which is already known to be valid. If the {@code specifier} might be + * invalid, use {@link #from(String)} instead. + * + *

    The specifier must be in one of these formats: + *

      + *
    • A domain name, like {@code google.com} + *
    • A IPv4 address string, like {@code 127.0.0.1} + *
    • An IPv6 address string with or without brackets, like + * {@code [2001:db8::1]} or {@code 2001:db8::1} + *
    + * + * @throws IllegalArgumentException if the specifier is not valid. + */ + public static HostSpecifier fromValid(String specifier) { + // Verify that no port was specified, and strip optional brackets from + // IPv6 literals. + final HostAndPort parsedHost = HostAndPort.fromString(specifier); + Preconditions.checkArgument(!parsedHost.hasPort()); + final String host = parsedHost.getHostText(); + + // Try to interpret the specifier as an IP address. Note we build + // the address rather than using the .is* methods because we want to + // use InetAddresses.toUriString to convert the result to a string in + // canonical form. + InetAddress addr = null; + try { + addr = InetAddresses.forString(host); + } catch (IllegalArgumentException e) { + // It is not an IPv4 or IPv6 literal + } + + if (addr != null) { + return new HostSpecifier(InetAddresses.toUriString(addr)); + } + + // It is not any kind of IP address; must be a domain name or invalid. + + // TODO(user): different versions of this for different factories? + final InternetDomainName domain = InternetDomainName.from(host); + + if (domain.hasPublicSuffix()) { + return new HostSpecifier(domain.name()); + } + + throw new IllegalArgumentException( + "Domain name does not have a recognized public suffix: " + host); + } + + /** + * Attempts to return a {@code HostSpecifier} for the given string, throwing + * an exception if parsing fails. Always use this method in preference to + * {@link #fromValid(String)} for a specifier that is not already known to be + * valid. + * + * @throws ParseException if the specifier is not valid. + */ + public static HostSpecifier from(String specifier) + throws ParseException { + try { + return fromValid(specifier); + } catch (IllegalArgumentException e) { + // Since the IAE can originate at several different points inside + // fromValid(), we implement this method in terms of that one rather + // than the reverse. + + ParseException parseException = + new ParseException("Invalid host specifier: " + specifier, 0); + parseException.initCause(e); + throw parseException; + } + } + + /** + * Determines whether {@code specifier} represents a valid + * {@link HostSpecifier} as described in the documentation for + * {@link #fromValid(String)}. + */ + public static boolean isValid(String specifier) { + try { + fromValid(specifier); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + + if (other instanceof HostSpecifier) { + final HostSpecifier that = (HostSpecifier) other; + return this.canonicalForm.equals(that.canonicalForm); + } + + return false; + } + + @Override + public int hashCode() { + return canonicalForm.hashCode(); + } + + /** + * Returns a string representation of the host specifier suitable for + * inclusion in a URI. If the host specifier is a domain name, the + * string will be normalized to all lower case. If the specifier was + * an IPv6 address without brackets, brackets are added so that the + * result will be usable in the host part of a URI. + */ + @Override + public String toString() { + return canonicalForm; + } +} diff --git a/guava/src/com/google/common/net/HttpHeaders.java b/guava/src/com/google/common/net/HttpHeaders.java new file mode 100644 index 0000000..4bb6f9f --- /dev/null +++ b/guava/src/com/google/common/net/HttpHeaders.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Contains constant definitions for the HTTP header field names. See: + * + * + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@Beta +@GwtCompatible +public final class HttpHeaders { + private HttpHeaders() {} + + // HTTP Request and Response header fields + + /** The HTTP Cache-Control header field name. */ + public static final String CACHE_CONTROL = "Cache-Control"; + /** The HTTP Content-Length header field name. */ + public static final String CONTENT_LENGTH = "Content-Length"; + /** The HTTP Content-Type header field name. */ + public static final String CONTENT_TYPE = "Content-Type"; + /** The HTTP Date header field name. */ + public static final String DATE = "Date"; + /** The HTTP Pragma header field name. */ + public static final String PRAGMA = "Pragma"; + /** The HTTP Via header field name. */ + public static final String VIA = "Via"; + /** The HTTP Warning header field name. */ + public static final String WARNING = "Warning"; + + // HTTP Request header fields + + /** The HTTP Accept header field name. */ + public static final String ACCEPT = "Accept"; + /** The HTTP Accept-Charset header field name. */ + public static final String ACCEPT_CHARSET = "Accept-Charset"; + /** The HTTP Accept-Encoding header field name. */ + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + /** The HTTP Accept-Language header field name. */ + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + /** The HTTP Access-Control-Request-Headers header field name. */ + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + /** The HTTP Access-Control-Request-Method header field name. */ + public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + /** The HTTP Authorization header field name. */ + public static final String AUTHORIZATION = "Authorization"; + /** The HTTP Connection header field name. */ + public static final String CONNECTION = "Connection"; + /** The HTTP Cookie header field name. */ + public static final String COOKIE = "Cookie"; + /** The HTTP Expect header field name. */ + public static final String EXPECT = "Expect"; + /** The HTTP From header field name. */ + public static final String FROM = "From"; + /** The HTTP Host header field name. */ + public static final String HOST = "Host"; + /** The HTTP If-Match header field name. */ + public static final String IF_MATCH = "If-Match"; + /** The HTTP If-Modified-Since header field name. */ + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + /** The HTTP If-None-Match header field name. */ + public static final String IF_NONE_MATCH = "If-None-Match"; + /** The HTTP If-Range header field name. */ + public static final String IF_RANGE = "If-Range"; + /** The HTTP If-Unmodified-Since header field name. */ + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + /** The HTTP Last-Event-ID header field name. */ + public static final String LAST_EVENT_ID = "Last-Event-ID"; + /** The HTTP Max-Forwards header field name. */ + public static final String MAX_FORWARDS = "Max-Forwards"; + /** The HTTP Origin header field name. */ + public static final String ORIGIN = "Origin"; + /** The HTTP Proxy-Authorization header field name. */ + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + /** The HTTP Range header field name. */ + public static final String RANGE = "Range"; + /** The HTTP Referer header field name. */ + public static final String REFERER = "Referer"; + /** The HTTP TE header field name. */ + public static final String TE = "TE"; + /** The HTTP Upgrade header field name. */ + public static final String UPGRADE = "Upgrade"; + /** The HTTP User-Agent header field name. */ + public static final String USER_AGENT = "User-Agent"; + + // HTTP Response header fields + + /** The HTTP Accept-Ranges header field name. */ + public static final String ACCEPT_RANGES = "Accept-Ranges"; + /** The HTTP Access-Control-Allow-Headers header field name. */ + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + /** The HTTP Access-Control-Allow-Methods header field name. */ + public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + /** The HTTP Access-Control-Allow-Origin header field name. */ + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + /** The HTTP Access-Control-Allow-Credentials header field name. */ + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + /** The HTTP Access-Control-Expose-Headers header field name. */ + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + /** The HTTP Access-Control-Max-Age header field name. */ + public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + /** The HTTP Age header field name. */ + public static final String AGE = "Age"; + /** The HTTP Allow header field name. */ + public static final String ALLOW = "Allow"; + /** The HTTP Content-Disposition header field name. */ + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + /** The HTTP Content-Encoding header field name. */ + public static final String CONTENT_ENCODING = "Content-Encoding"; + /** The HTTP Content-Language header field name. */ + public static final String CONTENT_LANGUAGE = "Content-Language"; + /** The HTTP Content-Location header field name. */ + public static final String CONTENT_LOCATION = "Content-Location"; + /** The HTTP Content-MD5 header field name. */ + public static final String CONTENT_MD5 = "Content-MD5"; + /** The HTTP Content-Range header field name. */ + public static final String CONTENT_RANGE = "Content-Range"; + /** The HTTP ETag header field name. */ + public static final String ETAG = "ETag"; + /** The HTTP Expires header field name. */ + public static final String EXPIRES = "Expires"; + /** The HTTP Last-Modified header field name. */ + public static final String LAST_MODIFIED = "Last-Modified"; + /** The HTTP Link header field name. */ + public static final String LINK = "Link"; + /** The HTTP Location header field name. */ + public static final String LOCATION = "Location"; + /** The HTTP P3P header field name. Limited browser support. */ + public static final String P3P = "P3P"; + /** The HTTP Proxy-Authenticate header field name. */ + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + /** The HTTP Refresh header field name. Non-standard header supported by most browsers. */ + public static final String REFRESH = "Refresh"; + /** The HTTP Retry-After header field name. */ + public static final String RETRY_AFTER = "Retry-After"; + /** The HTTP Server header field name. */ + public static final String SERVER = "Server"; + /** The HTTP Set-Cookie header field name. */ + public static final String SET_COOKIE = "Set-Cookie"; + /** The HTTP Set-Cookie2 header field name. */ + public static final String SET_COOKIE2 = "Set-Cookie2"; + /** The HTTP Trailer header field name. */ + public static final String TRAILER = "Trailer"; + /** The HTTP Transfer-Encoding header field name. */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + /** The HTTP Vary header field name. */ + public static final String VARY = "Vary"; + /** The HTTP WWW-Authenticate header field name. */ + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + + // Common, non-standard HTTP header fields + + /** The HTTP DNT header field name. */ + public static final String DNT = "DNT"; + /** The HTTP X-Content-Type-Options header field name. */ + public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + /** The HTTP X-Do-Not-Track header field name. */ + public static final String X_DO_NOT_TRACK = "X-Do-Not-Track"; + /** The HTTP X-Forwarded-For header field name. */ + public static final String X_FORWARDED_FOR = "X-Forwarded-For"; + /** The HTTP X-Forwarded-Proto header field name. */ + public static final String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + /** The HTTP X-Frame-Options header field name. */ + public static final String X_FRAME_OPTIONS = "X-Frame-Options"; + /** The HTTP X-Powered-By header field name. */ + public static final String X_POWERED_BY = "X-Powered-By"; + /** The HTTP X-Requested-With header field name. */ + public static final String X_REQUESTED_WITH = "X-Requested-With"; + /** The HTTP X-User-IP header field name. */ + public static final String X_USER_IP = "X-User-IP"; + /** The HTTP X-XSS-Protection header field name. */ + public static final String X_XSS_PROTECTION = "X-XSS-Protection"; + +} diff --git a/guava/src/com/google/common/net/InetAddresses.java b/guava/src/com/google/common/net/InetAddresses.java new file mode 100644 index 0000000..8eddd0d --- /dev/null +++ b/guava/src/com/google/common/net/InetAddresses.java @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link InetAddress} instances. + * + *

    Important note: Unlike {@code InetAddress.getByName()}, the + * methods of this class never cause DNS services to be accessed. For + * this reason, you should prefer these methods as much as possible over + * their JDK equivalents whenever you are expecting to handle only + * IP address string literals -- there is no blocking DNS penalty for a + * malformed string. + * + *

    When dealing with {@link Inet4Address} and {@link Inet6Address} + * objects as byte arrays (vis. {@code InetAddress.getAddress()}) they + * are 4 and 16 bytes in length, respectively, and represent the address + * in network byte order. + * + *

    Examples of IP addresses and their byte representations: + *

      + *
    • The IPv4 loopback address, {@code "127.0.0.1"}.
      + * {@code 7f 00 00 01} + * + *
    • The IPv6 loopback address, {@code "::1"}.
      + * {@code 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01} + * + *
    • From the IPv6 reserved documentation prefix ({@code 2001:db8::/32}), + * {@code "2001:db8::1"}.
      + * {@code 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01} + * + *
    • An IPv6 "IPv4 compatible" (or "compat") address, + * {@code "::192.168.0.1"}.
      + * {@code 00 00 00 00 00 00 00 00 00 00 00 00 c0 a8 00 01} + * + *
    • An IPv6 "IPv4 mapped" address, {@code "::ffff:192.168.0.1"}.
      + * {@code 00 00 00 00 00 00 00 00 00 00 ff ff c0 a8 00 01} + *
    + * + *

    A few notes about IPv6 "IPv4 mapped" addresses and their observed + * use in Java. + *

    + * "IPv4 mapped" addresses were originally a representation of IPv4 + * addresses for use on an IPv6 socket that could receive both IPv4 + * and IPv6 connections (by disabling the {@code IPV6_V6ONLY} socket + * option on an IPv6 socket). Yes, it's confusing. Nevertheless, + * these "mapped" addresses were never supposed to be seen on the + * wire. That assumption was dropped, some say mistakenly, in later + * RFCs with the apparent aim of making IPv4-to-IPv6 transition simpler. + * + *

    Technically one can create a 128bit IPv6 address with the wire + * format of a "mapped" address, as shown above, and transmit it in an + * IPv6 packet header. However, Java's InetAddress creation methods + * appear to adhere doggedly to the original intent of the "mapped" + * address: all "mapped" addresses return {@link Inet4Address} objects. + * + *

    For added safety, it is common for IPv6 network operators to filter + * all packets where either the source or destination address appears to + * be a "compat" or "mapped" address. Filtering suggestions usually + * recommend discarding any packets with source or destination addresses + * in the invalid range {@code ::/3}, which includes both of these bizarre + * address formats. For more information on "bogons", including lists + * of IPv6 bogon space, see: + * + *

    + * + * @author Erik Kline + * @since 5.0 + */ +@Beta +public final class InetAddresses { + private static final int IPV4_PART_COUNT = 4; + private static final int IPV6_PART_COUNT = 8; + private static final Inet4Address LOOPBACK4 = (Inet4Address) forString("127.0.0.1"); + private static final Inet4Address ANY4 = (Inet4Address) forString("0.0.0.0"); + + private InetAddresses() {} + + /** + * Returns an {@link Inet4Address}, given a byte array representation of the IPv4 address. + * + * @param bytes byte array representing an IPv4 address (should be of length 4) + * @return {@link Inet4Address} corresponding to the supplied byte array + * @throws IllegalArgumentException if a valid {@link Inet4Address} can not be created + */ + private static Inet4Address getInet4Address(byte[] bytes) { + Preconditions.checkArgument(bytes.length == 4, + "Byte array has invalid length for an IPv4 address: %s != 4.", + bytes.length); + + // Given a 4-byte array, this cast should always succeed. + return (Inet4Address) bytesToInetAddress(bytes); + } + + /** + * Returns the {@link InetAddress} having the given string representation. + * + *

    This deliberately avoids all nameservice lookups (e.g. no DNS). + * + * @param ipString {@code String} containing an IPv4 or IPv6 string literal, e.g. + * {@code "192.168.0.1"} or {@code "2001:db8::1"} + * @return {@link InetAddress} representing the argument + * @throws IllegalArgumentException if the argument is not a valid IP string literal + */ + public static InetAddress forString(String ipString) { + byte[] addr = ipStringToBytes(ipString); + + // The argument was malformed, i.e. not an IP string literal. + if (addr == null) { + throw new IllegalArgumentException( + String.format("'%s' is not an IP string literal.", ipString)); + } + + return bytesToInetAddress(addr); + } + + /** + * Returns {@code true} if the supplied string is a valid IP string + * literal, {@code false} otherwise. + * + * @param ipString {@code String} to evaluated as an IP string literal + * @return {@code true} if the argument is a valid IP string literal + */ + public static boolean isInetAddress(String ipString) { + return ipStringToBytes(ipString) != null; + } + + private static byte[] ipStringToBytes(String ipString) { + // Make a first pass to categorize the characters in this string. + boolean hasColon = false; + boolean hasDot = false; + for (int i = 0; i < ipString.length(); i++) { + char c = ipString.charAt(i); + if (c == '.') { + hasDot = true; + } else if (c == ':') { + if (hasDot) { + return null; // Colons must not appear after dots. + } + hasColon = true; + } else if (Character.digit(c, 16) == -1) { + return null; // Everything else must be a decimal or hex digit. + } + } + + // Now decide which address family to parse. + if (hasColon) { + if (hasDot) { + ipString = convertDottedQuadToHex(ipString); + if (ipString == null) { + return null; + } + } + return textToNumericFormatV6(ipString); + } else if (hasDot) { + return textToNumericFormatV4(ipString); + } + return null; + } + + private static byte[] textToNumericFormatV4(String ipString) { + String[] address = ipString.split("\\.", IPV4_PART_COUNT + 1); + if (address.length != IPV4_PART_COUNT) { + return null; + } + + byte[] bytes = new byte[IPV4_PART_COUNT]; + try { + for (int i = 0; i < bytes.length; i++) { + bytes[i] = parseOctet(address[i]); + } + } catch (NumberFormatException ex) { + return null; + } + + return bytes; + } + + private static byte[] textToNumericFormatV6(String ipString) { + // An address can have [2..8] colons, and N colons make N+1 parts. + String[] parts = ipString.split(":", IPV6_PART_COUNT + 2); + if (parts.length < 3 || parts.length > IPV6_PART_COUNT + 1) { + return null; + } + + // Disregarding the endpoints, find "::" with nothing in between. + // This indicates that a run of zeroes has been skipped. + int skipIndex = -1; + for (int i = 1; i < parts.length - 1; i++) { + if (parts[i].length() == 0) { + if (skipIndex >= 0) { + return null; // Can't have more than one :: + } + skipIndex = i; + } + } + + int partsHi; // Number of parts to copy from above/before the "::" + int partsLo; // Number of parts to copy from below/after the "::" + if (skipIndex >= 0) { + // If we found a "::", then check if it also covers the endpoints. + partsHi = skipIndex; + partsLo = parts.length - skipIndex - 1; + if (parts[0].length() == 0 && --partsHi != 0) { + return null; // ^: requires ^:: + } + if (parts[parts.length - 1].length() == 0 && --partsLo != 0) { + return null; // :$ requires ::$ + } + } else { + // Otherwise, allocate the entire address to partsHi. The endpoints + // could still be empty, but parseHextet() will check for that. + partsHi = parts.length; + partsLo = 0; + } + + // If we found a ::, then we must have skipped at least one part. + // Otherwise, we must have exactly the right number of parts. + int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo); + if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) { + return null; + } + + // Now parse the hextets into a byte array. + ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT); + try { + for (int i = 0; i < partsHi; i++) { + rawBytes.putShort(parseHextet(parts[i])); + } + for (int i = 0; i < partsSkipped; i++) { + rawBytes.putShort((short) 0); + } + for (int i = partsLo; i > 0; i--) { + rawBytes.putShort(parseHextet(parts[parts.length - i])); + } + } catch (NumberFormatException ex) { + return null; + } + return rawBytes.array(); + } + + private static String convertDottedQuadToHex(String ipString) { + int lastColon = ipString.lastIndexOf(':'); + String initialPart = ipString.substring(0, lastColon + 1); + String dottedQuad = ipString.substring(lastColon + 1); + byte[] quad = textToNumericFormatV4(dottedQuad); + if (quad == null) { + return null; + } + String penultimate = Integer.toHexString(((quad[0] & 0xff) << 8) | (quad[1] & 0xff)); + String ultimate = Integer.toHexString(((quad[2] & 0xff) << 8) | (quad[3] & 0xff)); + return initialPart + penultimate + ":" + ultimate; + } + + private static byte parseOctet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int octet = Integer.parseInt(ipPart); + // Disallow leading zeroes, because no clear standard exists on + // whether these should be interpreted as decimal or octal. + if (octet > 255 || (ipPart.startsWith("0") && ipPart.length() > 1)) { + throw new NumberFormatException(); + } + return (byte) octet; + } + + private static short parseHextet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int hextet = Integer.parseInt(ipPart, 16); + if (hextet > 0xffff) { + throw new NumberFormatException(); + } + return (short) hextet; + } + + /** + * Convert a byte array into an InetAddress. + * + * {@link InetAddress#getByAddress} is documented as throwing a checked + * exception "if IP address if of illegal length." We replace it with + * an unchecked exception, for use by callers who already know that addr + * is an array of length 4 or 16. + * + * @param addr the raw 4-byte or 16-byte IP address in big-endian order + * @return an InetAddress object created from the raw IP address + */ + private static InetAddress bytesToInetAddress(byte[] addr) { + try { + return InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + throw new AssertionError(e); + } + } + + /** + * Returns the string representation of an {@link InetAddress}. + * + *

    For IPv4 addresses, this is identical to + * {@link InetAddress#getHostAddress()}, but for IPv6 addresses, the output + * follows RFC 5952 + * section 4. The main difference is that this method uses "::" for zero + * compression, while Java's version uses the uncompressed form. + * + *

    This method uses hexadecimal for all IPv6 addresses, including + * IPv4-mapped IPv6 addresses such as "::c000:201". The output does not + * include a Scope ID. + * + * @param ip {@link InetAddress} to be converted to an address string + * @return {@code String} containing the text-formatted IP address + * @since 10.0 + */ + public static String toAddrString(InetAddress ip) { + Preconditions.checkNotNull(ip); + if (ip instanceof Inet4Address) { + // For IPv4, Java's formatting is good enough. + return ip.getHostAddress(); + } + Preconditions.checkArgument(ip instanceof Inet6Address); + byte[] bytes = ip.getAddress(); + int[] hextets = new int[IPV6_PART_COUNT]; + for (int i = 0; i < hextets.length; i++) { + hextets[i] = Ints.fromBytes( + (byte) 0, (byte) 0, bytes[2 * i], bytes[2 * i + 1]); + } + compressLongestRunOfZeroes(hextets); + return hextetsToIPv6String(hextets); + } + + /** + * Identify and mark the longest run of zeroes in an IPv6 address. + * + *

    Only runs of two or more hextets are considered. In case of a tie, the + * leftmost run wins. If a qualifying run is found, its hextets are replaced + * by the sentinel value -1. + * + * @param hextets {@code int[]} mutable array of eight 16-bit hextets + */ + private static void compressLongestRunOfZeroes(int[] hextets) { + int bestRunStart = -1; + int bestRunLength = -1; + int runStart = -1; + for (int i = 0; i < hextets.length + 1; i++) { + if (i < hextets.length && hextets[i] == 0) { + if (runStart < 0) { + runStart = i; + } + } else if (runStart >= 0) { + int runLength = i - runStart; + if (runLength > bestRunLength) { + bestRunStart = runStart; + bestRunLength = runLength; + } + runStart = -1; + } + } + if (bestRunLength >= 2) { + Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1); + } + } + + /** + * Convert a list of hextets into a human-readable IPv6 address. + * + *

    In order for "::" compression to work, the input should contain negative + * sentinel values in place of the elided zeroes. + * + * @param hextets {@code int[]} array of eight 16-bit hextets, or -1s + */ + private static String hextetsToIPv6String(int[] hextets) { + /* + * While scanning the array, handle these state transitions: + * start->num => "num" start->gap => "::" + * num->num => ":num" num->gap => "::" + * gap->num => "num" gap->gap => "" + */ + StringBuilder buf = new StringBuilder(39); + boolean lastWasNumber = false; + for (int i = 0; i < hextets.length; i++) { + boolean thisIsNumber = hextets[i] >= 0; + if (thisIsNumber) { + if (lastWasNumber) { + buf.append(':'); + } + buf.append(Integer.toHexString(hextets[i])); + } else { + if (i == 0 || lastWasNumber) { + buf.append("::"); + } + } + lastWasNumber = thisIsNumber; + } + return buf.toString(); + } + + /** + * Returns the string representation of an {@link InetAddress} suitable + * for inclusion in a URI. + * + *

    For IPv4 addresses, this is identical to + * {@link InetAddress#getHostAddress()}, but for IPv6 addresses it + * compresses zeroes and surrounds the text with square brackets; for example + * {@code "[2001:db8::1]"}. + * + *

    Per section 3.2.2 of + * http://tools.ietf.org/html/rfc3986, + * a URI containing an IPv6 string literal is of the form + * {@code "http://[2001:db8::1]:8888/index.html"}. + * + *

    Use of either {@link InetAddresses#toAddrString}, + * {@link InetAddress#getHostAddress()}, or this method is recommended over + * {@link InetAddress#toString()} when an IP address string literal is + * desired. This is because {@link InetAddress#toString()} prints the + * hostname and the IP address string joined by a "/". + * + * @param ip {@link InetAddress} to be converted to URI string literal + * @return {@code String} containing URI-safe string literal + */ + public static String toUriString(InetAddress ip) { + if (ip instanceof Inet6Address) { + return "[" + toAddrString(ip) + "]"; + } + return toAddrString(ip); + } + + /** + * Returns an InetAddress representing the literal IPv4 or IPv6 host + * portion of a URL, encoded in the format specified by RFC 3986 section 3.2.2. + * + *

    This function is similar to {@link InetAddresses#forString(String)}, + * however, it requires that IPv6 addresses are surrounded by square brackets. + * + *

    This function is the inverse of + * {@link InetAddresses#toUriString(java.net.InetAddress)}. + * + * @param hostAddr A RFC 3986 section 3.2.2 encoded IPv4 or IPv6 address + * @return an InetAddress representing the address in {@code hostAddr} + * @throws IllegalArgumentException if {@code hostAddr} is not a valid + * IPv4 address, or IPv6 address surrounded by square brackets + */ + public static InetAddress forUriString(String hostAddr) { + Preconditions.checkNotNull(hostAddr); + + // Decide if this should be an IPv6 or IPv4 address. + String ipString; + int expectBytes; + if (hostAddr.startsWith("[") && hostAddr.endsWith("]")) { + ipString = hostAddr.substring(1, hostAddr.length() - 1); + expectBytes = 16; + } else { + ipString = hostAddr; + expectBytes = 4; + } + + // Parse the address, and make sure the length/version is correct. + byte[] addr = ipStringToBytes(ipString); + if (addr == null || addr.length != expectBytes) { + throw new IllegalArgumentException( + String.format("Not a valid URI IP literal: '%s'", hostAddr)); + } + + return bytesToInetAddress(addr); + } + + /** + * Returns {@code true} if the supplied string is a valid URI IP string + * literal, {@code false} otherwise. + * + * @param ipString {@code String} to evaluated as an IP URI host string literal + * @return {@code true} if the argument is a valid IP URI host + */ + public static boolean isUriInetAddress(String ipString) { + try { + forUriString(ipString); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Evaluates whether the argument is an IPv6 "compat" address. + * + *

    An "IPv4 compatible", or "compat", address is one with 96 leading + * bits of zero, with the remaining 32 bits interpreted as an + * IPv4 address. These are conventionally represented in string + * literals as {@code "::192.168.0.1"}, though {@code "::c0a8:1"} is + * also considered an IPv4 compatible address (and equivalent to + * {@code "::192.168.0.1"}). + * + *

    For more on IPv4 compatible addresses see section 2.5.5.1 of + * http://tools.ietf.org/html/rfc4291 + * + *

    NOTE: This method is different from + * {@link Inet6Address#isIPv4CompatibleAddress} in that it more + * correctly classifies {@code "::"} and {@code "::1"} as + * proper IPv6 addresses (which they are), NOT IPv4 compatible + * addresses (which they are generally NOT considered to be). + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 compatible address format + * @return {@code true} if the argument is a valid "compat" address + */ + public static boolean isCompatIPv4Address(Inet6Address ip) { + if (!ip.isIPv4CompatibleAddress()) { + return false; + } + + byte[] bytes = ip.getAddress(); + if ((bytes[12] == 0) && (bytes[13] == 0) && (bytes[14] == 0) + && ((bytes[15] == 0) || (bytes[15] == 1))) { + return false; + } + + return true; + } + + /** + * Returns the IPv4 address embedded in an IPv4 compatible address. + * + * @param ip {@link Inet6Address} to be examined for an embedded IPv4 address + * @return {@link Inet4Address} of the embedded IPv4 address + * @throws IllegalArgumentException if the argument is not a valid IPv4 compatible address + */ + public static Inet4Address getCompatIPv4Address(Inet6Address ip) { + Preconditions.checkArgument(isCompatIPv4Address(ip), + "Address '%s' is not IPv4-compatible.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + } + + /** + * Evaluates whether the argument is a 6to4 address. + * + *

    6to4 addresses begin with the {@code "2002::/16"} prefix. + * The next 32 bits are the IPv4 address of the host to which + * IPv6-in-IPv4 tunneled packets should be routed. + * + *

    For more on 6to4 addresses see section 2 of + * http://tools.ietf.org/html/rfc3056 + * + * @param ip {@link Inet6Address} to be examined for 6to4 address format + * @return {@code true} if the argument is a 6to4 address + */ + public static boolean is6to4Address(Inet6Address ip) { + byte[] bytes = ip.getAddress(); + return (bytes[0] == (byte) 0x20) && (bytes[1] == (byte) 0x02); + } + + /** + * Returns the IPv4 address embedded in a 6to4 address. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 in 6to4 address + * @return {@link Inet4Address} of embedded IPv4 in 6to4 address + * @throws IllegalArgumentException if the argument is not a valid IPv6 6to4 address + */ + public static Inet4Address get6to4IPv4Address(Inet6Address ip) { + Preconditions.checkArgument(is6to4Address(ip), + "Address '%s' is not a 6to4 address.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 2, 6)); + } + + /** + * A simple immutable data class to encapsulate the information to be found in a + * Teredo address. + * + *

    All of the fields in this class are encoded in various portions + * of the IPv6 address as part of the protocol. More protocols details + * can be found at: + * http://en.wikipedia.org/wiki/Teredo_tunneling. + * + *

    The RFC can be found here: + * http://tools.ietf.org/html/rfc4380. + * + * @since 5.0 + */ + @Beta + public static final class TeredoInfo { + private final Inet4Address server; + private final Inet4Address client; + private final int port; + private final int flags; + + /** + * Constructs a TeredoInfo instance. + * + *

    Both server and client can be {@code null}, in which case the + * value {@code "0.0.0.0"} will be assumed. + * + * @throws IllegalArgumentException if either of the {@code port} or the {@code flags} + * arguments are out of range of an unsigned short + */ + // TODO: why is this public? + public TeredoInfo( + @Nullable Inet4Address server, @Nullable Inet4Address client, int port, int flags) { + Preconditions.checkArgument((port >= 0) && (port <= 0xffff), + "port '%s' is out of range (0 <= port <= 0xffff)", port); + Preconditions.checkArgument((flags >= 0) && (flags <= 0xffff), + "flags '%s' is out of range (0 <= flags <= 0xffff)", flags); + + this.server = Objects.firstNonNull(server, ANY4); + this.client = Objects.firstNonNull(client, ANY4); + this.port = port; + this.flags = flags; + } + + public Inet4Address getServer() { + return server; + } + + public Inet4Address getClient() { + return client; + } + + public int getPort() { + return port; + } + + public int getFlags() { + return flags; + } + } + + /** + * Evaluates whether the argument is a Teredo address. + * + *

    Teredo addresses begin with the {@code "2001::/32"} prefix. + * + * @param ip {@link Inet6Address} to be examined for Teredo address format + * @return {@code true} if the argument is a Teredo address + */ + public static boolean isTeredoAddress(Inet6Address ip) { + byte[] bytes = ip.getAddress(); + return (bytes[0] == (byte) 0x20) && (bytes[1] == (byte) 0x01) + && (bytes[2] == 0) && (bytes[3] == 0); + } + + /** + * Returns the Teredo information embedded in a Teredo address. + * + * @param ip {@link Inet6Address} to be examined for embedded Teredo information + * @return extracted {@code TeredoInfo} + * @throws IllegalArgumentException if the argument is not a valid IPv6 Teredo address + */ + public static TeredoInfo getTeredoInfo(Inet6Address ip) { + Preconditions.checkArgument(isTeredoAddress(ip), + "Address '%s' is not a Teredo address.", toAddrString(ip)); + + byte[] bytes = ip.getAddress(); + Inet4Address server = getInet4Address(Arrays.copyOfRange(bytes, 4, 8)); + + int flags = ByteStreams.newDataInput(bytes, 8).readShort() & 0xffff; + + // Teredo obfuscates the mapped client port, per section 4 of the RFC. + int port = ~ByteStreams.newDataInput(bytes, 10).readShort() & 0xffff; + + byte[] clientBytes = Arrays.copyOfRange(bytes, 12, 16); + for (int i = 0; i < clientBytes.length; i++) { + // Teredo obfuscates the mapped client IP, per section 4 of the RFC. + clientBytes[i] = (byte) ~clientBytes[i]; + } + Inet4Address client = getInet4Address(clientBytes); + + return new TeredoInfo(server, client, port, flags); + } + + /** + * Evaluates whether the argument is an ISATAP address. + * + *

    From RFC 5214: "ISATAP interface identifiers are constructed in + * Modified EUI-64 format [...] by concatenating the 24-bit IANA OUI + * (00-00-5E), the 8-bit hexadecimal value 0xFE, and a 32-bit IPv4 + * address in network byte order [...]" + * + *

    For more on ISATAP addresses see section 6.1 of + * http://tools.ietf.org/html/rfc5214 + * + * @param ip {@link Inet6Address} to be examined for ISATAP address format + * @return {@code true} if the argument is an ISATAP address + */ + public static boolean isIsatapAddress(Inet6Address ip) { + + // If it's a Teredo address with the right port (41217, or 0xa101) + // which would be encoded as 0x5efe then it can't be an ISATAP address. + if (isTeredoAddress(ip)) { + return false; + } + + byte[] bytes = ip.getAddress(); + + if ((bytes[8] | (byte) 0x03) != (byte) 0x03) { + + // Verify that high byte of the 64 bit identifier is zero, modulo + // the U/L and G bits, with which we are not concerned. + return false; + } + + return (bytes[9] == (byte) 0x00) && (bytes[10] == (byte) 0x5e) + && (bytes[11] == (byte) 0xfe); + } + + /** + * Returns the IPv4 address embedded in an ISATAP address. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 in ISATAP address + * @return {@link Inet4Address} of embedded IPv4 in an ISATAP address + * @throws IllegalArgumentException if the argument is not a valid IPv6 ISATAP address + */ + public static Inet4Address getIsatapIPv4Address(Inet6Address ip) { + Preconditions.checkArgument(isIsatapAddress(ip), + "Address '%s' is not an ISATAP address.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + } + + /** + * Examines the Inet6Address to determine if it is an IPv6 address of one + * of the specified address types that contain an embedded IPv4 address. + * + *

    NOTE: ISATAP addresses are explicitly excluded from this method + * due to their trivial spoofability. With other transition addresses + * spoofing involves (at least) infection of one's BGP routing table. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address + * @return {@code true} if there is an embedded IPv4 client address + * @since 7.0 + */ + public static boolean hasEmbeddedIPv4ClientAddress(Inet6Address ip) { + return isCompatIPv4Address(ip) || is6to4Address(ip) || isTeredoAddress(ip); + } + + /** + * Examines the Inet6Address to extract the embedded IPv4 client address + * if the InetAddress is an IPv6 address of one of the specified address + * types that contain an embedded IPv4 address. + * + *

    NOTE: ISATAP addresses are explicitly excluded from this method + * due to their trivial spoofability. With other transition addresses + * spoofing involves (at least) infection of one's BGP routing table. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address + * @return {@link Inet4Address} of embedded IPv4 client address + * @throws IllegalArgumentException if the argument does not have a valid embedded IPv4 address + */ + public static Inet4Address getEmbeddedIPv4ClientAddress(Inet6Address ip) { + if (isCompatIPv4Address(ip)) { + return getCompatIPv4Address(ip); + } + + if (is6to4Address(ip)) { + return get6to4IPv4Address(ip); + } + + if (isTeredoAddress(ip)) { + return getTeredoInfo(ip).getClient(); + } + + throw new IllegalArgumentException( + String.format("'%s' has no embedded IPv4 address.", toAddrString(ip))); + } + + /** + * Evaluates whether the argument is an "IPv4 mapped" IPv6 address. + * + *

    An "IPv4 mapped" address is anything in the range ::ffff:0:0/96 + * (sometimes written as ::ffff:0.0.0.0/96), with the last 32 bits + * interpreted as an IPv4 address. + * + *

    For more on IPv4 mapped addresses see section 2.5.5.2 of + * http://tools.ietf.org/html/rfc4291 + * + *

    Note: This method takes a {@code String} argument because + * {@link InetAddress} automatically collapses mapped addresses to IPv4. + * (It is actually possible to avoid this using one of the obscure + * {@link Inet6Address} methods, but it would be unwise to depend on such + * a poorly-documented feature.) + * + * @param ipString {@code String} to be examined for embedded IPv4-mapped IPv6 address format + * @return {@code true} if the argument is a valid "mapped" address + * @since 10.0 + */ + public static boolean isMappedIPv4Address(String ipString) { + byte[] bytes = ipStringToBytes(ipString); + if (bytes != null && bytes.length == 16) { + for (int i = 0; i < 10; i++) { + if (bytes[i] != 0) { + return false; + } + } + for (int i = 10; i < 12; i++) { + if (bytes[i] != (byte) 0xff) { + return false; + } + } + return true; + } + return false; + } + + /** + * Coerces an IPv6 address into an IPv4 address. + * + *

    HACK: As long as applications continue to use IPv4 addresses for + * indexing into tables, accounting, et cetera, it may be necessary to + * coerce IPv6 addresses into IPv4 addresses. This function does + * so by hashing the upper 64 bits into {@code 224.0.0.0/3} + * (64 bits into 29 bits). + * + *

    A "coerced" IPv4 address is equivalent to itself. + * + *

    NOTE: This function is failsafe for security purposes: ALL IPv6 + * addresses (except localhost (::1)) are hashed to avoid the security + * risk associated with extracting an embedded IPv4 address that might + * permit elevated privileges. + * + * @param ip {@link InetAddress} to "coerce" + * @return {@link Inet4Address} represented "coerced" address + * @since 7.0 + */ + public static Inet4Address getCoercedIPv4Address(InetAddress ip) { + if (ip instanceof Inet4Address) { + return (Inet4Address) ip; + } + + // Special cases: + byte[] bytes = ip.getAddress(); + boolean leadingBytesOfZero = true; + for (int i = 0; i < 15; ++i) { + if (bytes[i] != 0) { + leadingBytesOfZero = false; + break; + } + } + if (leadingBytesOfZero && (bytes[15] == 1)) { + return LOOPBACK4; // ::1 + } else if (leadingBytesOfZero && (bytes[15] == 0)) { + return ANY4; // ::0 + } + + Inet6Address ip6 = (Inet6Address) ip; + long addressAsLong = 0; + if (hasEmbeddedIPv4ClientAddress(ip6)) { + addressAsLong = getEmbeddedIPv4ClientAddress(ip6).hashCode(); + } else { + + // Just extract the high 64 bits (assuming the rest is user-modifiable). + addressAsLong = ByteBuffer.wrap(ip6.getAddress(), 0, 8).getLong(); + } + + // Many strategies for hashing are possible. This might suffice for now. + int coercedHash = Hashing.murmur3_32().hashLong(addressAsLong).asInt(); + + // Squash into 224/4 Multicast and 240/4 Reserved space (i.e. 224/3). + coercedHash |= 0xe0000000; + + // Fixup to avoid some "illegal" values. Currently the only potential + // illegal value is 255.255.255.255. + if (coercedHash == 0xffffffff) { + coercedHash = 0xfffffffe; + } + + return getInet4Address(Ints.toByteArray(coercedHash)); + } + + /** + * Returns an integer representing an IPv4 address regardless of + * whether the supplied argument is an IPv4 address or not. + * + *

    IPv6 addresses are coerced to IPv4 addresses before being + * converted to integers. + * + *

    As long as there are applications that assume that all IP addresses + * are IPv4 addresses and can therefore be converted safely to integers + * (for whatever purpose) this function can be used to handle IPv6 + * addresses as well until the application is suitably fixed. + * + *

    NOTE: an IPv6 address coerced to an IPv4 address can only be used + * for such purposes as rudimentary identification or indexing into a + * collection of real {@link InetAddress}es. They cannot be used as + * real addresses for the purposes of network communication. + * + * @param ip {@link InetAddress} to convert + * @return {@code int}, "coerced" if ip is not an IPv4 address + * @since 7.0 + */ + public static int coerceToInteger(InetAddress ip) { + return ByteStreams.newDataInput(getCoercedIPv4Address(ip).getAddress()).readInt(); + } + + /** + * Returns an Inet4Address having the integer value specified by + * the argument. + * + * @param address {@code int}, the 32bit integer address to be converted + * @return {@link Inet4Address} equivalent of the argument + */ + public static Inet4Address fromInteger(int address) { + return getInet4Address(Ints.toByteArray(address)); + } + + /** + * Returns an address from a little-endian ordered byte array + * (the opposite of what {@link InetAddress#getByAddress} expects). + * + *

    IPv4 address byte array must be 4 bytes long and IPv6 byte array + * must be 16 bytes long. + * + * @param addr the raw IP address in little-endian byte order + * @return an InetAddress object created from the raw IP address + * @throws UnknownHostException if IP address is of illegal length + */ + public static InetAddress fromLittleEndianByteArray(byte[] addr) throws UnknownHostException { + byte[] reversed = new byte[addr.length]; + for (int i = 0; i < addr.length; i++) { + reversed[i] = addr[addr.length - i - 1]; + } + return InetAddress.getByAddress(reversed); + } + + /** + * Returns a new InetAddress that is one more than the passed in address. + * This method works for both IPv4 and IPv6 addresses. + * + * @param address the InetAddress to increment + * @return a new InetAddress that is one more than the passed in address + * @throws IllegalArgumentException if InetAddress is at the end of its range + * @since 10.0 + */ + public static InetAddress increment(InetAddress address) { + byte[] addr = address.getAddress(); + int i = addr.length - 1; + while (i >= 0 && addr[i] == (byte) 0xff) { + addr[i] = 0; + i--; + } + + Preconditions.checkArgument(i >= 0, "Incrementing %s would wrap.", address); + + addr[i]++; + return bytesToInetAddress(addr); + } + + /** + * Returns true if the InetAddress is either 255.255.255.255 for IPv4 or + * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6. + * + * @return true if the InetAddress is either 255.255.255.255 for IPv4 or + * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6 + * @since 10.0 + */ + public static boolean isMaximum(InetAddress address) { + byte[] addr = address.getAddress(); + for (int i = 0; i < addr.length; i++) { + if (addr[i] != (byte) 0xff) { + return false; + } + } + return true; + } +} diff --git a/guava/src/com/google/common/net/InternetDomainName.java b/guava/src/com/google/common/net/InternetDomainName.java new file mode 100644 index 0000000..ace7cf2 --- /dev/null +++ b/guava/src/com/google/common/net/InternetDomainName.java @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * An immutable well-formed internet domain name, such as {@code com} or {@code + * foo.co.uk}. Only syntactic analysis is performed; no DNS lookups or other + * network interactions take place. Thus there is no guarantee that the domain + * actually exists on the internet. + * + *

    One common use of this class is to determine whether a given string is + * likely to represent an addressable domain on the web -- that is, for a + * candidate string {@code "xxx"}, might browsing to {@code "http://xxx/"} + * result in a webpage being displayed? In the past, this test was frequently + * done by determining whether the domain ended with a {@linkplain + * #isPublicSuffix() public suffix} but was not itself a public suffix. However, + * this test is no longer accurate. There are many domains which are both public + * suffixes and addressable as hosts; {@code "uk.com"} is one example. As a + * result, the only useful test to determine if a domain is a plausible web host + * is {@link #hasPublicSuffix()}. This will return {@code true} for many domains + * which (currently) are not hosts, such as {@code "com"}), but given that any + * public suffix may become a host without warning, it is better to err on the + * side of permissiveness and thus avoid spurious rejection of valid sites. + * + *

    During construction, names are normalized in two ways: + *

      + *
    1. ASCII uppercase characters are converted to lowercase. + *
    2. Unicode dot separators other than the ASCII period ({@code '.'}) are + * converted to the ASCII period. + *
    + * The normalized values will be returned from {@link #name()} and + * {@link #parts()}, and will be reflected in the result of + * {@link #equals(Object)}. + * + *

    + * internationalized domain names such as {@code 网络.cn} are supported, as + * are the equivalent IDNA + * Punycode-encoded versions. + * + * @author Craig Berry + * @since 5.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class InternetDomainName { + + private static final CharMatcher DOTS_MATCHER = + CharMatcher.anyOf(".\u3002\uFF0E\uFF61"); + private static final Splitter DOT_SPLITTER = Splitter.on('.'); + private static final Joiner DOT_JOINER = Joiner.on('.'); + + /** + * Value of {@link #publicSuffixIndex} which indicates that no public suffix + * was found. + */ + private static final int NO_PUBLIC_SUFFIX_FOUND = -1; + + private static final String DOT_REGEX = "\\."; + + /** + * Maximum parts (labels) in a domain name. This value arises from + * the 255-octet limit described in + * RFC 2181 part 11 with + * the fact that the encoding of each part occupies at least two bytes + * (dot plus label externally, length byte plus label internally). Thus, if + * all labels have the minimum size of one byte, 127 of them will fit. + */ + private static final int MAX_PARTS = 127; + + /** + * Maximum length of a full domain name, including separators, and + * leaving room for the root label. See + * RFC 2181 part 11. + */ + private static final int MAX_LENGTH = 253; + + /** + * Maximum size of a single part of a domain name. See + * RFC 2181 part 11. + */ + private static final int MAX_DOMAIN_PART_LENGTH = 63; + + /** + * The full domain name, converted to lower case. + */ + private final String name; + + /** + * The parts of the domain name, converted to lower case. + */ + private final ImmutableList parts; + + /** + * The index in the {@link #parts()} list at which the public suffix begins. + * For example, for the domain name {@code www.google.co.uk}, the value would + * be 2 (the index of the {@code co} part). The value is negative + * (specifically, {@link #NO_PUBLIC_SUFFIX_FOUND}) if no public suffix was + * found. + */ + private final int publicSuffixIndex; + + /** + * Constructor used to implement {@link #from(String)}, and from subclasses. + */ + InternetDomainName(String name) { + // Normalize: + // * ASCII characters to lowercase + // * All dot-like characters to '.' + // * Strip trailing '.' + + name = Ascii.toLowerCase(DOTS_MATCHER.replaceFrom(name, '.')); + + if (name.endsWith(".")) { + name = name.substring(0, name.length() - 1); + } + + checkArgument(name.length() <= MAX_LENGTH, + "Domain name too long: '%s':", name); + this.name = name; + + this.parts = ImmutableList.copyOf(DOT_SPLITTER.split(name)); + checkArgument(parts.size() <= MAX_PARTS, + "Domain has too many parts: '%s'", name); + checkArgument(validateSyntax(parts), "Not a valid domain name: '%s'", name); + + this.publicSuffixIndex = findPublicSuffix(); + } + + /** + * Returns the index of the leftmost part of the public suffix, or -1 if not + * found. Note that the value defined as the "public suffix" may not be a + * public suffix according to {@link #isPublicSuffix()} if the domain ends + * with an excluded domain pattern such as {@code "nhs.uk"}. + */ + private int findPublicSuffix() { + final int partsSize = parts.size(); + + for (int i = 0; i < partsSize; i++) { + String ancestorName = DOT_JOINER.join(parts.subList(i, partsSize)); + + if (TldPatterns.EXACT.contains(ancestorName)) { + return i; + } + + // Excluded domains (e.g. !nhs.uk) use the next highest + // domain as the effective public suffix (e.g. uk). + + if (TldPatterns.EXCLUDED.contains(ancestorName)) { + return i + 1; + } + + if (matchesWildcardPublicSuffix(ancestorName)) { + return i; + } + } + + return NO_PUBLIC_SUFFIX_FOUND; + } + + /** + * A deprecated synonym for {@link #from(String)}. + * + * @param domain A domain name (not IP address) + * @throws IllegalArgumentException if {@code name} is not syntactically valid + * according to {@link #isValidLenient} + * @since 8.0 (previously named {@code from}) + * @deprecated Use {@link #from(String)} + */ + @Deprecated + public static InternetDomainName fromLenient(String domain) { + return from(domain); + } + + /** + * Returns an instance of {@link InternetDomainName} after lenient + * validation. Specifically, validation against RFC 3490 + * ("Internationalizing Domain Names in Applications") is skipped, while + * validation against RFC 1035 is relaxed in + * the following ways: + *

      + *
    • Any part containing non-ASCII characters is considered valid. + *
    • Underscores ('_') are permitted wherever dashes ('-') are permitted. + *
    • Parts other than the final part may start with a digit. + *
    + * + * + * @param domain A domain name (not IP address) + * @throws IllegalArgumentException if {@code name} is not syntactically valid + * according to {@link #isValid} + * @since 10.0 (previously named {@code fromLenient}) + */ + public static InternetDomainName from(String domain) { + return new InternetDomainName(checkNotNull(domain)); + } + + /** + * Validation method used by {@from} to ensure that the domain name is + * syntactically valid according to RFC 1035. + * + * @return Is the domain name syntactically valid? + */ + private static boolean validateSyntax(List parts) { + final int lastIndex = parts.size() - 1; + + // Validate the last part specially, as it has different syntax rules. + + if (!validatePart(parts.get(lastIndex), true)) { + return false; + } + + for (int i = 0; i < lastIndex; i++) { + String part = parts.get(i); + if (!validatePart(part, false)) { + return false; + } + } + + return true; + } + + private static final CharMatcher DASH_MATCHER = CharMatcher.anyOf("-_"); + + private static final CharMatcher PART_CHAR_MATCHER = + CharMatcher.JAVA_LETTER_OR_DIGIT.or(DASH_MATCHER); + + /** + * Helper method for {@link #validateSyntax(List)}. Validates that one part of + * a domain name is valid. + * + * @param part The domain name part to be validated + * @param isFinalPart Is this the final (rightmost) domain part? + * @return Whether the part is valid + */ + private static boolean validatePart(String part, boolean isFinalPart) { + + // These tests could be collapsed into one big boolean expression, but + // they have been left as independent tests for clarity. + + if (part.length() < 1 || part.length() > MAX_DOMAIN_PART_LENGTH) { + return false; + } + + /* + * GWT claims to support java.lang.Character's char-classification methods, + * but it actually only works for ASCII. So for now, assume any non-ASCII + * characters are valid. The only place this seems to be documented is here: + * http://osdir.com/ml/GoogleWebToolkitContributors/2010-03/msg00178.html + * + *

    ASCII characters in the part are expected to be valid per RFC 1035, + * with underscore also being allowed due to widespread practice. + */ + + String asciiChars = CharMatcher.ASCII.retainFrom(part); + + if (!PART_CHAR_MATCHER.matchesAllOf(asciiChars)) { + return false; + } + + // No initial or final dashes or underscores. + + if (DASH_MATCHER.matches(part.charAt(0)) + || DASH_MATCHER.matches(part.charAt(part.length() - 1))) { + return false; + } + + /* + * Note that we allow (in contravention of a strict interpretation of the + * relevant RFCs) domain parts other than the last may begin with a digit + * (for example, "3com.com"). It's important to disallow an initial digit in + * the last part; it's the only thing that stops an IPv4 numeric address + * like 127.0.0.1 from looking like a valid domain name. + */ + + if (isFinalPart && CharMatcher.DIGIT.matches(part.charAt(0))) { + return false; + } + + return true; + } + + /** + * Returns the domain name, normalized to all lower case. + */ + public String name() { + return name; + } + + /** + * Returns the individual components of this domain name, normalized to all + * lower case. For example, for the domain name {@code mail.google.com}, this + * method returns the list {@code ["mail", "google", "com"]}. + */ + public ImmutableList parts() { + return parts; + } + + /** + * Indicates whether this domain name represents a public suffix, as + * defined by the Mozilla Foundation's + * Public Suffix List (PSL). A public + * suffix is one under which Internet users can directly register names, such + * as {@code com}, {@code co.uk} or {@code pvt.k12.wy.us}. Examples of domain + * names that are not public suffixes include {@code google}, {@code + * google.com} and {@code foo.co.uk}. + * + * @return {@code true} if this domain name appears exactly on the public + * suffix list + * @since 6.0 + */ + public boolean isPublicSuffix() { + return publicSuffixIndex == 0; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isPublicSuffix() + * public suffix}, including if it is a public suffix itself. For example, + * returns {@code true} for {@code www.google.com}, {@code foo.co.uk} and + * {@code com}, but not for {@code google} or {@code google.foo}. This is + * the recommended method for determining whether a domain is potentially an + * addressable host. + * + * @since 6.0 + */ + public boolean hasPublicSuffix() { + return publicSuffixIndex != NO_PUBLIC_SUFFIX_FOUND; + } + + /** + * Returns the {@linkplain #isPublicSuffix() public suffix} portion of the + * domain name, or {@code null} if no public suffix is present. + * + * @since 6.0 + */ + public InternetDomainName publicSuffix() { + return hasPublicSuffix() ? ancestor(publicSuffixIndex) : null; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isPublicSuffix() + * public suffix}, while not being a public suffix itself. For example, + * returns {@code true} for {@code www.google.com}, {@code foo.co.uk} and + * {@code bar.ca.us}, but not for {@code google}, {@code com}, or {@code + * google.foo}. + * + *

    Warning: a {@code false} result from this method does not imply + * that the domain does not represent an addressable host, as many public + * suffixes are also addressable hosts. Use {@link #hasPublicSuffix()} for + * that test. + * + *

    This method can be used to determine whether it will probably be + * possible to set cookies on the domain, though even that depends on + * individual browsers' implementations of cookie controls. See + * RFC 2109 for details. + * + * @since 6.0 + */ + public boolean isUnderPublicSuffix() { + return publicSuffixIndex > 0; + } + + /** + * Indicates whether this domain name is composed of exactly one subdomain + * component followed by a {@linkplain #isPublicSuffix() public suffix}. For + * example, returns {@code true} for {@code google.com} and {@code foo.co.uk}, + * but not for {@code www.google.com} or {@code co.uk}. + * + *

    Warning: A {@code true} result from this method does not imply + * that the domain is at the highest level which is addressable as a host, as + * many public suffixes are also addressable hosts. For example, the domain + * {@code bar.uk.com} has a public suffix of {@code uk.com}, so it would + * return {@code true} from this method. But {@code uk.com} is itself an + * addressable host. + * + *

    This method can be used to determine whether a domain is probably the + * highest level for which cookies may be set, though even that depends on + * individual browsers' implementations of cookie controls. See + * RFC 2109 for details. + * + * @since 6.0 + */ + public boolean isTopPrivateDomain() { + return publicSuffixIndex == 1; + } + + /** + * Returns the portion of this domain name that is one level beneath the + * public suffix. For example, for {@code x.adwords.google.co.uk} it returns + * {@code google.co.uk}, since {@code co.uk} is a public suffix. + * + *

    If {@link #isTopPrivateDomain()} is true, the current domain name + * instance is returned. + * + *

    This method should not be used to determine the topmost parent domain + * which is addressable as a host, as many public suffixes are also + * addressable hosts. For example, the domain {@code foo.bar.uk.com} has + * a public suffix of {@code uk.com}, so it would return {@code bar.uk.com} + * from this method. But {@code uk.com} is itself an addressable host. + * + *

    This method can be used to determine the probable highest level parent + * domain for which cookies may be set, though even that depends on individual + * browsers' implementations of cookie controls. + * + * @throws IllegalStateException if this domain does not end with a + * public suffix + * @since 6.0 + */ + public InternetDomainName topPrivateDomain() { + if (isTopPrivateDomain()) { + return this; + } + checkState(isUnderPublicSuffix(), "Not under a public suffix: %s", name); + return ancestor(publicSuffixIndex - 1); + } + + /** + * Indicates whether this domain is composed of two or more parts. + */ + public boolean hasParent() { + return parts.size() > 1; + } + + /** + * Returns an {@code InternetDomainName} that is the immediate ancestor of + * this one; that is, the current domain with the leftmost part removed. For + * example, the parent of {@code www.google.com} is {@code google.com}. + * + * @throws IllegalStateException if the domain has no parent, as determined + * by {@link #hasParent} + */ + public InternetDomainName parent() { + checkState(hasParent(), "Domain '%s' has no parent", name); + return ancestor(1); + } + + /** + * Returns the ancestor of the current domain at the given number of levels + * "higher" (rightward) in the subdomain list. The number of levels must be + * non-negative, and less than {@code N-1}, where {@code N} is the number of + * parts in the domain. + * + *

    TODO: Reasonable candidate for addition to public API. + */ + private InternetDomainName ancestor(int levels) { + return from(DOT_JOINER.join(parts.subList(levels, parts.size()))); + } + + /** + * Creates and returns a new {@code InternetDomainName} by prepending the + * argument and a dot to the current name. For example, {@code + * InternetDomainName.from("foo.com").child("www.bar")} returns a new + * {@code InternetDomainName} with the value {@code www.bar.foo.com}. Only + * lenient validation is performed, as described {@link #from(String) here}. + * + * @throws NullPointerException if leftParts is null + * @throws IllegalArgumentException if the resulting name is not valid + */ + public InternetDomainName child(String leftParts) { + return from(checkNotNull(leftParts) + "." + name); + } + + /** + * A deprecated synonym for {@link #isValid(String)}. + * + * @since 8.0 (previously named {@code isValid}) + * @deprecated Use {@link #isValid(String)} instead + */ + @Deprecated + public static boolean isValidLenient(String name) { + return isValid(name); + } + + /** + * Indicates whether the argument is a syntactically valid domain name using + * lenient validation. Specifically, validation against RFC 3490 + * ("Internationalizing Domain Names in Applications") is skipped. + * + *

    The following two code snippets are equivalent: + * + *

       {@code
    +   *
    +   *   domainName = InternetDomainName.isValid(name)
    +   *       ? InternetDomainName.from(name)
    +   *       : DEFAULT_DOMAIN;
    +   *   }
    + * + *
       {@code
    +   *
    +   *   try {
    +   *     domainName = InternetDomainName.from(name);
    +   *   } catch (IllegalArgumentException e) {
    +   *     domainName = DEFAULT_DOMAIN;
    +   *   }}
    + * + * @since 8.0 (previously named {@code isValidLenient}) + */ + public static boolean isValid(String name) { + try { + from(name); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Does the domain name match one of the "wildcard" patterns (e.g. + * {@code "*.ar"})? + */ + private static boolean matchesWildcardPublicSuffix(String domain) { + final String[] pieces = domain.split(DOT_REGEX, 2); + return pieces.length == 2 && TldPatterns.UNDER.contains(pieces[1]); + } + + // TODO: specify this to return the same as name(); remove name() + @Override + public String toString() { + return Objects.toStringHelper(this).add("name", name).toString(); + } + + /** + * Equality testing is based on the text supplied by the caller, + * after normalization as described in the class documentation. For + * example, a non-ASCII Unicode domain name and the Punycode version + * of the same domain name would not be considered equal. + * + */ + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + + if (object instanceof InternetDomainName) { + InternetDomainName that = (InternetDomainName) object; + return this.name.equals(that.name); + } + + return false; + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/guava/src/com/google/common/net/MediaType.java b/guava/src/com/google/common/net/MediaType.java new file mode 100644 index 0000000..c804b88 --- /dev/null +++ b/guava/src/com/google/common/net/MediaType.java @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import static com.google.common.base.CharMatcher.ASCII; +import static com.google.common.base.CharMatcher.JAVA_ISO_CONTROL; +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Joiner.MapJoiner; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Represents an Internet Media Type + * (also known as a MIME Type or Content Type). This class also supports the concept of media ranges + * defined by HTTP/1.1. + * As such, the {@code *} character is treated as a wildcard and is used to represent any acceptable + * type or subtype value. A media type may not have wildcard type with a declared subtype. The + * {@code *} character has no special meaning as part of a parameter. All values for type, subtype, + * parameter attributes or parameter values must be valid according to RFCs + * 2045 and + * 2046. + * + *

    All portions of the media type that are case-insensitive (type, subtype, parameter attributes) + * are normalized to lowercase. The value of the {@code charset} parameter is normalized to + * lowercase, but all others are left as-is. + * + *

    Note that this specifically does not represent the value of the MIME + * {@code Content-Type} header and as such has no support for header-specific considerations such as + * line folding and comments. + * + *

    For media types that take a charset the predefined constants default to UTF-8 and have a + * "_UTF_8" suffix. To get a version without a character set, use {@link #withoutParameters}. + * + * @since 12.0 + * + * @author Gregory Kick + */ +@Beta +@GwtCompatible +@Immutable +public final class MediaType { + private static final String CHARSET_ATTRIBUTE = "charset"; + private static final ImmutableListMultimap UTF_8_CONSTANT_PARAMETERS = + ImmutableListMultimap.of(CHARSET_ATTRIBUTE, Ascii.toLowerCase(UTF_8.name())); + + /** Matcher for type, subtype and attributes. */ + private static final CharMatcher TOKEN_MATCHER = ASCII.and(JAVA_ISO_CONTROL.negate()) + .and(CharMatcher.isNot(' ')) + .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?=")); + private static final CharMatcher QUOTED_TEXT_MATCHER = ASCII + .and(CharMatcher.noneOf("\"\\\r")); + /* + * This matches the same characters as linear-white-space from RFC 822, but we make no effort to + * enforce any particular rules with regards to line folding as stated in the class docs. + */ + private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n"); + + // TODO(gak): make these public? + private static final String APPLICATION_TYPE = "application"; + private static final String AUDIO_TYPE = "audio"; + private static final String IMAGE_TYPE = "image"; + private static final String TEXT_TYPE = "text"; + private static final String VIDEO_TYPE = "video"; + + private static final String WILDCARD = "*"; + + /* + * The following constants are grouped by their type and ordered alphabetically by the constant + * name within that type. The constant name should be a sensible identifier that is closest to the + * "common name" of the media. This is often, but not necessarily the same as the subtype. + * + * Be sure to declare all constants with the type and subtype in all lowercase. + * + * When adding constants, be sure to add an entry into the KNOWN_TYPES map. For types that + * take a charset (e.g. all text/* types), default to UTF-8 and suffix with "_UTF_8". + */ + + public static final MediaType ANY_TYPE = createConstant(WILDCARD, WILDCARD); + public static final MediaType ANY_TEXT_TYPE = createConstant(TEXT_TYPE, WILDCARD); + public static final MediaType ANY_IMAGE_TYPE = createConstant(IMAGE_TYPE, WILDCARD); + public static final MediaType ANY_AUDIO_TYPE = createConstant(AUDIO_TYPE, WILDCARD); + public static final MediaType ANY_VIDEO_TYPE = createConstant(VIDEO_TYPE, WILDCARD); + public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD); + + /* text types */ + public static final MediaType CACHE_MANIFEST_UTF_8 = + createConstantUtf8(TEXT_TYPE, "cache-manifest"); + public static final MediaType CSS_UTF_8 = createConstantUtf8(TEXT_TYPE, "css"); + public static final MediaType CSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "csv"); + public static final MediaType HTML_UTF_8 = createConstantUtf8(TEXT_TYPE, "html"); + public static final MediaType I_CALENDAR_UTF_8 = createConstantUtf8(TEXT_TYPE, "calendar"); + public static final MediaType PLAIN_TEXT_UTF_8 = createConstantUtf8(TEXT_TYPE, "plain"); + /** + * RFC 4329 declares + * {@link #JAVASCRIPT_UTF_8 application/javascript} to be the correct media type for JavaScript, + * but this may be necessary in certain situations for compatibility. + */ + public static final MediaType TEXT_JAVASCRIPT_UTF_8 = createConstantUtf8(TEXT_TYPE, "javascript"); + public static final MediaType VCARD_UTF_8 = createConstantUtf8(TEXT_TYPE, "vcard"); + public static final MediaType WML_UTF_8 = createConstantUtf8(TEXT_TYPE, "vnd.wap.wml"); + public static final MediaType XML_UTF_8 = createConstantUtf8(TEXT_TYPE, "xml"); + + /* image types */ + public static final MediaType BMP = createConstant(IMAGE_TYPE, "bmp"); + public static final MediaType GIF = createConstant(IMAGE_TYPE, "gif"); + public static final MediaType ICO = createConstant(IMAGE_TYPE, "vnd.microsoft.icon"); + public static final MediaType JPEG = createConstant(IMAGE_TYPE, "jpeg"); + public static final MediaType PNG = createConstant(IMAGE_TYPE, "png"); + public static final MediaType SVG_UTF_8 = createConstantUtf8(IMAGE_TYPE, "svg+xml"); + public static final MediaType TIFF = createConstant(IMAGE_TYPE, "tiff"); + public static final MediaType WEBP = createConstant(IMAGE_TYPE, "webp"); + + /* audio types */ + public static final MediaType MP4_AUDIO = createConstant(AUDIO_TYPE, "mp4"); + public static final MediaType MPEG_AUDIO = createConstant(AUDIO_TYPE, "mpeg"); + public static final MediaType OGG_AUDIO = createConstant(AUDIO_TYPE, "ogg"); + public static final MediaType WEBM_AUDIO = createConstant(AUDIO_TYPE, "webm"); + + /* video types */ + public static final MediaType MP4_VIDEO = createConstant(VIDEO_TYPE, "mp4"); + public static final MediaType MPEG_VIDEO = createConstant(VIDEO_TYPE, "mpeg"); + public static final MediaType OGG_VIDEO = createConstant(VIDEO_TYPE, "ogg"); + public static final MediaType QUICKTIME = createConstant(VIDEO_TYPE, "quicktime"); + public static final MediaType WEBM_VIDEO = createConstant(VIDEO_TYPE, "webm"); + public static final MediaType WMV = createConstant(VIDEO_TYPE, "x-ms-wmv"); + + /* application types */ + public static final MediaType ATOM_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "atom+xml"); + public static final MediaType BZIP2 = createConstant(APPLICATION_TYPE, "x-bzip2"); + public static final MediaType FORM_DATA = createConstant(APPLICATION_TYPE, + "x-www-form-urlencoded"); + public static final MediaType GZIP = createConstant(APPLICATION_TYPE, "x-gzip"); + /** + * RFC 4329 declares this to be the + * correct media type for JavaScript, but {@link #TEXT_JAVASCRIPT_UTF_8 text/javascript} may be + * necessary in certain situations for compatibility. + */ + public static final MediaType JAVASCRIPT_UTF_8 = + createConstantUtf8(APPLICATION_TYPE, "javascript"); + public static final MediaType JSON_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "json"); + public static final MediaType KML = createConstant(APPLICATION_TYPE, "vnd.google-earth.kml+xml"); + public static final MediaType KMZ = createConstant(APPLICATION_TYPE, "vnd.google-earth.kmz"); + public static final MediaType MBOX = createConstant(APPLICATION_TYPE, "mbox"); + public static final MediaType MICROSOFT_EXCEL = createConstant(APPLICATION_TYPE, "vnd.ms-excel"); + public static final MediaType MICROSOFT_POWERPOINT = + createConstant(APPLICATION_TYPE, "vnd.ms-powerpoint"); + public static final MediaType MICROSOFT_WORD = createConstant(APPLICATION_TYPE, "msword"); + public static final MediaType OCTET_STREAM = createConstant(APPLICATION_TYPE, "octet-stream"); + public static final MediaType OGG_CONTAINER = createConstant(APPLICATION_TYPE, "ogg"); + public static final MediaType OOXML_DOCUMENT = createConstant(APPLICATION_TYPE, + "vnd.openxmlformats-officedocument.wordprocessingml.document"); + public static final MediaType OOXML_PRESENTATION = createConstant(APPLICATION_TYPE, + "vnd.openxmlformats-officedocument.presentationml.presentation"); + public static final MediaType OOXML_SHEET = + createConstant(APPLICATION_TYPE, "vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + public static final MediaType OPENDOCUMENT_GRAPHICS = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.graphics"); + public static final MediaType OPENDOCUMENT_PRESENTATION = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.presentation"); + public static final MediaType OPENDOCUMENT_SPREADSHEET = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.spreadsheet"); + public static final MediaType OPENDOCUMENT_TEXT = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.text"); + public static final MediaType PDF = createConstant(APPLICATION_TYPE, "pdf"); + public static final MediaType POSTSCRIPT = createConstant(APPLICATION_TYPE, "postscript"); + public static final MediaType RTF_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rtf"); + public static final MediaType SHOCKWAVE_FLASH = createConstant(APPLICATION_TYPE, + "x-shockwave-flash"); + public static final MediaType SKETCHUP = createConstant(APPLICATION_TYPE, "vnd.sketchup.skp"); + public static final MediaType TAR = createConstant(APPLICATION_TYPE, "x-tar"); + public static final MediaType XHTML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xhtml+xml"); + public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip"); + + private static final ImmutableMap KNOWN_TYPES = + new ImmutableMap.Builder() + .put(ANY_TYPE, ANY_TYPE) + .put(ANY_TEXT_TYPE, ANY_TEXT_TYPE) + .put(ANY_IMAGE_TYPE, ANY_IMAGE_TYPE) + .put(ANY_AUDIO_TYPE, ANY_AUDIO_TYPE) + .put(ANY_VIDEO_TYPE, ANY_VIDEO_TYPE) + .put(ANY_APPLICATION_TYPE, ANY_APPLICATION_TYPE) + /* text types */ + .put(CACHE_MANIFEST_UTF_8, CACHE_MANIFEST_UTF_8) + .put(CSS_UTF_8, CSS_UTF_8) + .put(CSV_UTF_8, CSV_UTF_8) + .put(HTML_UTF_8, HTML_UTF_8) + .put(I_CALENDAR_UTF_8, I_CALENDAR_UTF_8) + .put(PLAIN_TEXT_UTF_8, PLAIN_TEXT_UTF_8) + .put(TEXT_JAVASCRIPT_UTF_8, TEXT_JAVASCRIPT_UTF_8) + .put(VCARD_UTF_8, VCARD_UTF_8) + .put(WML_UTF_8, WML_UTF_8) + .put(XML_UTF_8, XML_UTF_8) + /* image types */ + .put(BMP, BMP) + .put(GIF, GIF) + .put(ICO, ICO) + .put(JPEG, JPEG) + .put(PNG, PNG) + .put(SVG_UTF_8, SVG_UTF_8) + .put(TIFF, TIFF) + .put(WEBP, WEBP) + /* audio types */ + .put(MP4_AUDIO, MP4_AUDIO) + .put(MPEG_AUDIO, MPEG_AUDIO) + .put(OGG_AUDIO, OGG_AUDIO) + .put(WEBM_AUDIO, WEBM_AUDIO) + /* video types */ + .put(MP4_VIDEO, MP4_VIDEO) + .put(MPEG_VIDEO, MPEG_VIDEO) + .put(OGG_VIDEO, OGG_VIDEO) + .put(QUICKTIME, QUICKTIME) + .put(WEBM_VIDEO, WEBM_VIDEO) + .put(WMV, WMV) + /* application types */ + .put(ATOM_UTF_8, ATOM_UTF_8) + .put(BZIP2, BZIP2) + .put(FORM_DATA, FORM_DATA) + .put(GZIP, GZIP) + .put(JAVASCRIPT_UTF_8, JAVASCRIPT_UTF_8) + .put(JSON_UTF_8, JSON_UTF_8) + .put(KML, KML) + .put(KMZ, KMZ) + .put(MBOX, MBOX) + .put(MICROSOFT_EXCEL, MICROSOFT_EXCEL) + .put(MICROSOFT_POWERPOINT, MICROSOFT_POWERPOINT) + .put(MICROSOFT_WORD, MICROSOFT_WORD) + .put(OCTET_STREAM, OCTET_STREAM) + .put(OGG_CONTAINER, OGG_CONTAINER) + .put(OOXML_DOCUMENT, OOXML_DOCUMENT) + .put(OOXML_PRESENTATION, OOXML_PRESENTATION) + .put(OOXML_SHEET, OOXML_SHEET) + .put(OPENDOCUMENT_GRAPHICS, OPENDOCUMENT_GRAPHICS) + .put(OPENDOCUMENT_PRESENTATION, OPENDOCUMENT_PRESENTATION) + .put(OPENDOCUMENT_SPREADSHEET, OPENDOCUMENT_SPREADSHEET) + .put(OPENDOCUMENT_TEXT, OPENDOCUMENT_TEXT) + .put(PDF, PDF) + .put(POSTSCRIPT, POSTSCRIPT) + .put(RTF_UTF_8, RTF_UTF_8) + .put(SHOCKWAVE_FLASH, SHOCKWAVE_FLASH) + .put(SKETCHUP, SKETCHUP) + .put(TAR, TAR) + .put(XHTML_UTF_8, XHTML_UTF_8) + .put(ZIP, ZIP) + .build(); + + private final String type; + private final String subtype; + private final ImmutableListMultimap parameters; + + private MediaType(String type, String subtype, + ImmutableListMultimap parameters) { + this.type = type; + this.subtype = subtype; + this.parameters = parameters; + } + + private static MediaType createConstant(String type, String subtype) { + return new MediaType(type, subtype, ImmutableListMultimap.of()); + } + + private static MediaType createConstantUtf8(String type, String subtype) { + return new MediaType(type, subtype, UTF_8_CONSTANT_PARAMETERS); + } + + /** Returns the top-level media type. For example, {@code "text"} in {@code "text/plain"}. */ + public String type() { + return type; + } + + /** Returns the media subtype. For example, {@code "plain"} in {@code "text/plain"}. */ + public String subtype() { + return subtype; + } + + /** Returns a multimap containing the parameters of this media type. */ + public ImmutableListMultimap parameters() { + return parameters; + } + + private Map> parametersAsMap() { + return Maps.transformValues(parameters.asMap(), + new Function, ImmutableMultiset>() { + @Override public ImmutableMultiset apply(Collection input) { + return ImmutableMultiset.copyOf(input); + } + }); + } + + /** + * Returns an optional charset for the value of the charset parameter if it is specified. + * + * @throws IllegalStateException if multiple charset values have been set for this media type + * @throws IllegalCharsetNameException if a charset value is present, but illegal + * @throws UnsupportedCharsetException if a charset value is present, but no support is available + * in this instance of the Java virtual machine + */ + public Optional charset() { + ImmutableSet charsetValues = ImmutableSet.copyOf(parameters.get(CHARSET_ATTRIBUTE)); + switch (charsetValues.size()) { + case 0: + return Optional.absent(); + case 1: + return Optional.of(Charset.forName(Iterables.getOnlyElement(charsetValues))); + default: + throw new IllegalStateException("Multiple charset values defined: " + charsetValues); + } + } + + /** + * Returns a new instance with the same type and subtype as this instance, but without any + * parameters. + */ + public MediaType withoutParameters() { + return parameters.isEmpty() ? this : create(type, subtype); + } + + /** + * Replaces all parameters with the given parameters. + * + * @throws IllegalArgumentException if any parameter or value is invalid + */ + public MediaType withParameters(Multimap parameters) { + return create(type, subtype, parameters); + } + + /** + * Replaces all parameters with the given attribute with a single parameter with the + * given value. If multiple parameters with the same attributes are necessary use + * {@link #withParameters}. Prefer {@link #withCharset} for setting the {@code charset} parameter + * when using a {@link Charset} object. + * + * @throws IllegalArgumentException if either {@code attribute} or {@code value} is invalid + */ + public MediaType withParameter(String attribute, String value) { + checkNotNull(attribute); + checkNotNull(value); + String normalizedAttribute = normalizeToken(attribute); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + for (Entry entry : parameters.entries()) { + String key = entry.getKey(); + if (!normalizedAttribute.equals(key)) { + builder.put(key, entry.getValue()); + } + } + builder.put(normalizedAttribute, normalizeParameterValue(normalizedAttribute, value)); + MediaType mediaType = new MediaType(type, subtype, builder.build()); + // Return one of the constants if the media type is a known type. + return Objects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + } + + /** + * Returns a new instance with the same type and subtype as this instance, with the + * {@code charset} parameter set to the {@link Charset#name name} of the given charset. Only one + * {@code charset} parameter will be present on the new instance regardless of the number set on + * this one. + * + *

    If a charset must be specified that is not supported on this JVM (and thus is not + * representable as a {@link Charset} instance, use {@link #withParameter}. + */ + public MediaType withCharset(Charset charset) { + checkNotNull(charset); + return withParameter(CHARSET_ATTRIBUTE, charset.name()); + } + + /** Returns true if either the type or subtype is the wildcard. */ + public boolean hasWildcard() { + return WILDCARD.equals(type) || WILDCARD.equals(subtype); + } + + /** + * Returns {@code true} if this instance falls within the range (as defined by + * the HTTP Accept header) + * given by the argument according to three criteria: + * + *

      + *
    1. The type of the argument is the wildcard or equal to the type of this instance. + *
    2. The subtype of the argument is the wildcard or equal to the subtype of this instance. + *
    3. All of the parameters present in the argument are present in this instance. + *
    + * + * For example:
       {@code
    +   *   PLAIN_TEXT_UTF_8.is(PLAIN_TEXT_UTF_8) // true
    +   *   PLAIN_TEXT_UTF_8.is(HTML_UTF_8) // false
    +   *   PLAIN_TEXT_UTF_8.is(ANY_TYPE) // true
    +   *   PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE) // true
    +   *   PLAIN_TEXT_UTF_8.is(ANY_IMAGE_TYPE) // false
    +   *   PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_8)) // true
    +   *   PLAIN_TEXT_UTF_8.withoutParameters().is(ANY_TEXT_TYPE.withCharset(UTF_8)) // false
    +   *   PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_16)) // false}
    + * + *

    Note that while it is possible to have the same parameter declared multiple times within a + * media type this method does not consider the number of occurrences of a parameter. For + * example, {@code "text/plain; charset=UTF-8"} satisfies + * {@code "text/plain; charset=UTF-8; charset=UTF-8"}. + */ + public boolean is(MediaType mediaTypeRange) { + return (mediaTypeRange.type.equals(WILDCARD) || mediaTypeRange.type.equals(this.type)) + && (mediaTypeRange.subtype.equals(WILDCARD) || mediaTypeRange.subtype.equals(this.subtype)) + && this.parameters.entries().containsAll(mediaTypeRange.parameters.entries()); + } + + /** + * Creates a new media type with the given type and subtype. + * + * @throws IllegalArgumentException if type or subtype is invalid or if a wildcard is used for the + * type, but not the subtype. + */ + public static MediaType create(String type, String subtype) { + return create(type, subtype, ImmutableListMultimap.of()); + } + + /** + * Creates a media type with the "application" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createApplicationType(String subtype) { + return create(APPLICATION_TYPE, subtype); + } + + /** + * Creates a media type with the "audio" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createAudioType(String subtype) { + return create(AUDIO_TYPE, subtype); + } + + /** + * Creates a media type with the "image" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createImageType(String subtype) { + return create(IMAGE_TYPE, subtype); + } + + /** + * Creates a media type with the "text" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createTextType(String subtype) { + return create(TEXT_TYPE, subtype); + } + + /** + * Creates a media type with the "video" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createVideoType(String subtype) { + return create(VIDEO_TYPE, subtype); + } + + private static MediaType create(String type, String subtype, + Multimap parameters) { + checkNotNull(type); + checkNotNull(subtype); + checkNotNull(parameters); + String normalizedType = normalizeToken(type); + String normalizedSubtype = normalizeToken(subtype); + checkArgument(!WILDCARD.equals(normalizedType) || WILDCARD.equals(normalizedSubtype), + "A wildcard type cannot be used with a non-wildcard subtype"); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + for (Entry entry : parameters.entries()) { + String attribute = normalizeToken(entry.getKey()); + builder.put(attribute, normalizeParameterValue(attribute, entry.getValue())); + } + MediaType mediaType = new MediaType(normalizedType, normalizedSubtype, builder.build()); + // Return one of the constants if the media type is a known type. + return Objects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + } + + private static String normalizeToken(String token) { + checkArgument(TOKEN_MATCHER.matchesAllOf(token)); + return Ascii.toLowerCase(token); + } + + private static String normalizeParameterValue(String attribute, String value) { + return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value; + } + + /** + * Parses a media type from its string representation. + * + * @throws IllegalArgumentException if the input is not parsable + */ + public static MediaType parse(String input) { + checkNotNull(input); + Tokenizer tokenizer = new Tokenizer(input); + try { + String type = tokenizer.consumeToken(TOKEN_MATCHER); + tokenizer.consumeCharacter('/'); + String subtype = tokenizer.consumeToken(TOKEN_MATCHER); + ImmutableListMultimap.Builder parameters = ImmutableListMultimap.builder(); + while (tokenizer.hasMore()) { + tokenizer.consumeCharacter(';'); + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + String attribute = tokenizer.consumeToken(TOKEN_MATCHER); + tokenizer.consumeCharacter('='); + final String value; + if ('"' == tokenizer.previewChar()) { + tokenizer.consumeCharacter('"'); + StringBuilder valueBuilder = new StringBuilder(); + while ('"' != tokenizer.previewChar()) { + if ('\\' == tokenizer.previewChar()) { + tokenizer.consumeCharacter('\\'); + valueBuilder.append(tokenizer.consumeCharacter(ASCII)); + } else { + valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER)); + } + } + value = valueBuilder.toString(); + tokenizer.consumeCharacter('"'); + } else { + value = tokenizer.consumeToken(TOKEN_MATCHER); + } + parameters.put(attribute, value); + } + return create(type, subtype, parameters.build()); + } catch (IllegalStateException e) { + throw new IllegalArgumentException(e); + } + } + + private static final class Tokenizer { + final String input; + int position = 0; + + Tokenizer(String input) { + this.input = input; + } + + String consumeTokenIfPresent(CharMatcher matcher) { + checkState(hasMore()); + int startPosition = position; + position = matcher.negate().indexIn(input, startPosition); + return hasMore() ? input.substring(startPosition, position) : input.substring(startPosition); + } + + String consumeToken(CharMatcher matcher) { + int startPosition = position; + String token = consumeTokenIfPresent(matcher); + checkState(position != startPosition); + return token; + } + + char consumeCharacter(CharMatcher matcher) { + checkState(hasMore()); + char c = previewChar(); + checkState(matcher.matches(c)); + position++; + return c; + } + + char consumeCharacter(char c) { + checkState(hasMore()); + checkState(previewChar() == c); + position++; + return c; + } + + char previewChar() { + checkState(hasMore()); + return input.charAt(position); + } + + boolean hasMore() { + return (position >= 0) && (position < input.length()); + } + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof MediaType) { + MediaType that = (MediaType) obj; + return this.type.equals(that.type) + && this.subtype.equals(that.subtype) + // compare parameters regardless of order + && this.parametersAsMap().equals(that.parametersAsMap()); + } else { + return false; + } + } + + @Override public int hashCode() { + return Objects.hashCode(type, subtype, parametersAsMap()); + } + + private static final MapJoiner PARAMETER_JOINER = Joiner.on("; ").withKeyValueSeparator("="); + + /** + * Returns the string representation of this media type in the format described in RFC 2045. + */ + @Override public String toString() { + StringBuilder builder = new StringBuilder().append(type).append('/').append(subtype); + if (!parameters.isEmpty()) { + builder.append("; "); + Multimap quotedParameters = Multimaps.transformValues(parameters, + new Function() { + @Override public String apply(String value) { + return TOKEN_MATCHER.matchesAllOf(value) ? value : escapeAndQuote(value); + } + }); + PARAMETER_JOINER.appendTo(builder, quotedParameters.entries()); + } + return builder.toString(); + } + + private static String escapeAndQuote(String value) { + StringBuilder escaped = new StringBuilder(value.length() + 16).append('"'); + for (char ch : value.toCharArray()) { + if (ch == '\r' || ch == '\\' || ch == '"') { + escaped.append('\\'); + } + escaped.append(ch); + } + return escaped.append('"').toString(); + } + +} diff --git a/guava/src/com/google/common/net/TldPatterns.java b/guava/src/com/google/common/net/TldPatterns.java new file mode 100644 index 0000000..6fc4545 --- /dev/null +++ b/guava/src/com/google/common/net/TldPatterns.java @@ -0,0 +1,4633 @@ + +// GENERATED FILE - DO NOT EDIT + +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableSet; + +/** + * A generated static class containing public members which provide domain + * name patterns used in determining whether a given domain name is an + * effective top-level domain (TLD). + */ +@GwtCompatible +final class TldPatterns { + private TldPatterns() {} + + /** + * If a hostname is contained in this set, it is a TLD. + */ + static final ImmutableSet EXACT = ImmutableSet.of( + "ac", + "com.ac", + "edu.ac", + "gov.ac", + "net.ac", + "mil.ac", + "org.ac", + "ad", + "nom.ad", + "ae", + "co.ae", + "net.ae", + "org.ae", + "sch.ae", + "ac.ae", + "gov.ae", + "mil.ae", + "aero", + "accident-investigation.aero", + "accident-prevention.aero", + "aerobatic.aero", + "aeroclub.aero", + "aerodrome.aero", + "agents.aero", + "aircraft.aero", + "airline.aero", + "airport.aero", + "air-surveillance.aero", + "airtraffic.aero", + "air-traffic-control.aero", + "ambulance.aero", + "amusement.aero", + "association.aero", + "author.aero", + "ballooning.aero", + "broker.aero", + "caa.aero", + "cargo.aero", + "catering.aero", + "certification.aero", + "championship.aero", + "charter.aero", + "civilaviation.aero", + "club.aero", + "conference.aero", + "consultant.aero", + "consulting.aero", + "control.aero", + "council.aero", + "crew.aero", + "design.aero", + "dgca.aero", + "educator.aero", + "emergency.aero", + "engine.aero", + "engineer.aero", + "entertainment.aero", + "equipment.aero", + "exchange.aero", + "express.aero", + "federation.aero", + "flight.aero", + "freight.aero", + "fuel.aero", + "gliding.aero", + "government.aero", + "groundhandling.aero", + "group.aero", + "hanggliding.aero", + "homebuilt.aero", + "insurance.aero", + "journal.aero", + "journalist.aero", + "leasing.aero", + "logistics.aero", + "magazine.aero", + "maintenance.aero", + "marketplace.aero", + "media.aero", + "microlight.aero", + "modelling.aero", + "navigation.aero", + "parachuting.aero", + "paragliding.aero", + "passenger-association.aero", + "pilot.aero", + "press.aero", + "production.aero", + "recreation.aero", + "repbody.aero", + "res.aero", + "research.aero", + "rotorcraft.aero", + "safety.aero", + "scientist.aero", + "services.aero", + "show.aero", + "skydiving.aero", + "software.aero", + "student.aero", + "taxi.aero", + "trader.aero", + "trading.aero", + "trainer.aero", + "union.aero", + "workinggroup.aero", + "works.aero", + "af", + "gov.af", + "com.af", + "org.af", + "net.af", + "edu.af", + "ag", + "com.ag", + "org.ag", + "net.ag", + "co.ag", + "nom.ag", + "ai", + "off.ai", + "com.ai", + "net.ai", + "org.ai", + "al", + "com.al", + "edu.al", + "gov.al", + "mil.al", + "net.al", + "org.al", + "am", + "an", + "com.an", + "net.an", + "org.an", + "edu.an", + "ao", + "ed.ao", + "gv.ao", + "og.ao", + "co.ao", + "pb.ao", + "it.ao", + "aq", + "e164.arpa", + "in-addr.arpa", + "ip6.arpa", + "iris.arpa", + "uri.arpa", + "urn.arpa", + "as", + "gov.as", + "asia", + "at", + "ac.at", + "co.at", + "gv.at", + "or.at", + "com.au", + "net.au", + "org.au", + "edu.au", + "gov.au", + "csiro.au", + "asn.au", + "id.au", + "info.au", + "conf.au", + "oz.au", + "act.au", + "nsw.au", + "nt.au", + "qld.au", + "sa.au", + "tas.au", + "vic.au", + "wa.au", + "act.edu.au", + "nsw.edu.au", + "nt.edu.au", + "qld.edu.au", + "sa.edu.au", + "tas.edu.au", + "vic.edu.au", + "wa.edu.au", + "act.gov.au", + "nt.gov.au", + "qld.gov.au", + "sa.gov.au", + "tas.gov.au", + "vic.gov.au", + "wa.gov.au", + "aw", + "com.aw", + "ax", + "az", + "com.az", + "net.az", + "int.az", + "gov.az", + "org.az", + "edu.az", + "info.az", + "pp.az", + "mil.az", + "name.az", + "pro.az", + "biz.az", + "ba", + "org.ba", + "net.ba", + "edu.ba", + "gov.ba", + "mil.ba", + "unsa.ba", + "unbi.ba", + "co.ba", + "com.ba", + "rs.ba", + "bb", + "biz.bb", + "com.bb", + "edu.bb", + "gov.bb", + "info.bb", + "net.bb", + "org.bb", + "store.bb", + "be", + "ac.be", + "bf", + "gov.bf", + "bg", + "a.bg", + "b.bg", + "c.bg", + "d.bg", + "e.bg", + "f.bg", + "g.bg", + "h.bg", + "i.bg", + "j.bg", + "k.bg", + "l.bg", + "m.bg", + "n.bg", + "o.bg", + "p.bg", + "q.bg", + "r.bg", + "s.bg", + "t.bg", + "u.bg", + "v.bg", + "w.bg", + "x.bg", + "y.bg", + "z.bg", + "0.bg", + "1.bg", + "2.bg", + "3.bg", + "4.bg", + "5.bg", + "6.bg", + "7.bg", + "8.bg", + "9.bg", + "bh", + "com.bh", + "edu.bh", + "net.bh", + "org.bh", + "gov.bh", + "bi", + "co.bi", + "com.bi", + "edu.bi", + "or.bi", + "org.bi", + "biz", + "bj", + "asso.bj", + "barreau.bj", + "gouv.bj", + "bm", + "com.bm", + "edu.bm", + "gov.bm", + "net.bm", + "org.bm", + "bo", + "com.bo", + "edu.bo", + "gov.bo", + "gob.bo", + "int.bo", + "org.bo", + "net.bo", + "mil.bo", + "tv.bo", + "br", + "adm.br", + "adv.br", + "agr.br", + "am.br", + "arq.br", + "art.br", + "ato.br", + "b.br", + "bio.br", + "blog.br", + "bmd.br", + "can.br", + "cim.br", + "cng.br", + "cnt.br", + "com.br", + "coop.br", + "ecn.br", + "edu.br", + "emp.br", + "eng.br", + "esp.br", + "etc.br", + "eti.br", + "far.br", + "flog.br", + "fm.br", + "fnd.br", + "fot.br", + "fst.br", + "g12.br", + "ggf.br", + "gov.br", + "imb.br", + "ind.br", + "inf.br", + "jor.br", + "jus.br", + "lel.br", + "mat.br", + "med.br", + "mil.br", + "mus.br", + "net.br", + "nom.br", + "not.br", + "ntr.br", + "odo.br", + "org.br", + "ppg.br", + "pro.br", + "psc.br", + "psi.br", + "qsl.br", + "radio.br", + "rec.br", + "slg.br", + "srv.br", + "taxi.br", + "teo.br", + "tmp.br", + "trd.br", + "tur.br", + "tv.br", + "vet.br", + "vlog.br", + "wiki.br", + "zlg.br", + "bs", + "com.bs", + "net.bs", + "org.bs", + "edu.bs", + "gov.bs", + "bt", + "com.bt", + "edu.bt", + "gov.bt", + "net.bt", + "org.bt", + "bw", + "co.bw", + "org.bw", + "by", + "gov.by", + "mil.by", + "com.by", + "of.by", + "bz", + "com.bz", + "net.bz", + "org.bz", + "edu.bz", + "gov.bz", + "ca", + "ab.ca", + "bc.ca", + "mb.ca", + "nb.ca", + "nf.ca", + "nl.ca", + "ns.ca", + "nt.ca", + "nu.ca", + "on.ca", + "pe.ca", + "qc.ca", + "sk.ca", + "yk.ca", + "gc.ca", + "cat", + "cc", + "cd", + "gov.cd", + "cf", + "cg", + "ch", + "ci", + "org.ci", + "or.ci", + "com.ci", + "co.ci", + "edu.ci", + "ed.ci", + "ac.ci", + "net.ci", + "go.ci", + "asso.ci", + "a\u00e9roport.ci", + "xn--aroport-bya.ci", + "int.ci", + "presse.ci", + "md.ci", + "gouv.ci", + "cl", + "gov.cl", + "gob.cl", + "co.cl", + "mil.cl", + "cm", + "gov.cm", + "cn", + "ac.cn", + "com.cn", + "edu.cn", + "gov.cn", + "net.cn", + "org.cn", + "mil.cn", + "\u516c\u53f8.cn", + "xn--55qx5d.cn", + "\u7f51\u7edc.cn", + "xn--io0a7i.cn", + "\u7db2\u7d61.cn", + "xn--od0alg.cn", + "ah.cn", + "bj.cn", + "cq.cn", + "fj.cn", + "gd.cn", + "gs.cn", + "gz.cn", + "gx.cn", + "ha.cn", + "hb.cn", + "he.cn", + "hi.cn", + "hl.cn", + "hn.cn", + "jl.cn", + "js.cn", + "jx.cn", + "ln.cn", + "nm.cn", + "nx.cn", + "qh.cn", + "sc.cn", + "sd.cn", + "sh.cn", + "sn.cn", + "sx.cn", + "tj.cn", + "xj.cn", + "xz.cn", + "yn.cn", + "zj.cn", + "hk.cn", + "mo.cn", + "tw.cn", + "co", + "arts.co", + "com.co", + "edu.co", + "firm.co", + "gov.co", + "info.co", + "int.co", + "mil.co", + "net.co", + "nom.co", + "org.co", + "rec.co", + "web.co", + "com", + "coop", + "cr", + "ac.cr", + "co.cr", + "ed.cr", + "fi.cr", + "go.cr", + "or.cr", + "sa.cr", + "cu", + "com.cu", + "edu.cu", + "org.cu", + "net.cu", + "gov.cu", + "inf.cu", + "cv", + "cx", + "gov.cx", + "cz", + "de", + "dj", + "dk", + "dm", + "com.dm", + "net.dm", + "org.dm", + "edu.dm", + "gov.dm", + "do", + "art.do", + "com.do", + "edu.do", + "gob.do", + "gov.do", + "mil.do", + "net.do", + "org.do", + "sld.do", + "web.do", + "dz", + "com.dz", + "org.dz", + "net.dz", + "gov.dz", + "edu.dz", + "asso.dz", + "pol.dz", + "art.dz", + "ec", + "com.ec", + "info.ec", + "net.ec", + "fin.ec", + "k12.ec", + "med.ec", + "pro.ec", + "org.ec", + "edu.ec", + "gov.ec", + "gob.ec", + "mil.ec", + "edu", + "ee", + "edu.ee", + "gov.ee", + "riik.ee", + "lib.ee", + "med.ee", + "com.ee", + "pri.ee", + "aip.ee", + "org.ee", + "fie.ee", + "eg", + "com.eg", + "edu.eg", + "eun.eg", + "gov.eg", + "mil.eg", + "name.eg", + "net.eg", + "org.eg", + "sci.eg", + "es", + "com.es", + "nom.es", + "org.es", + "gob.es", + "edu.es", + "eu", + "fi", + "aland.fi", + "fm", + "fo", + "fr", + "com.fr", + "asso.fr", + "nom.fr", + "prd.fr", + "presse.fr", + "tm.fr", + "aeroport.fr", + "assedic.fr", + "avocat.fr", + "avoues.fr", + "cci.fr", + "chambagri.fr", + "chirurgiens-dentistes.fr", + "experts-comptables.fr", + "geometre-expert.fr", + "gouv.fr", + "greta.fr", + "huissier-justice.fr", + "medecin.fr", + "notaires.fr", + "pharmacien.fr", + "port.fr", + "veterinaire.fr", + "ga", + "gd", + "ge", + "com.ge", + "edu.ge", + "gov.ge", + "org.ge", + "mil.ge", + "net.ge", + "pvt.ge", + "gf", + "gg", + "co.gg", + "org.gg", + "net.gg", + "sch.gg", + "gov.gg", + "gh", + "com.gh", + "edu.gh", + "gov.gh", + "org.gh", + "mil.gh", + "gi", + "com.gi", + "ltd.gi", + "gov.gi", + "mod.gi", + "edu.gi", + "org.gi", + "gl", + "gm", + "ac.gn", + "com.gn", + "edu.gn", + "gov.gn", + "org.gn", + "net.gn", + "gov", + "gp", + "com.gp", + "net.gp", + "mobi.gp", + "edu.gp", + "org.gp", + "asso.gp", + "gq", + "gr", + "com.gr", + "edu.gr", + "net.gr", + "org.gr", + "gov.gr", + "gs", + "gw", + "gy", + "co.gy", + "com.gy", + "net.gy", + "hk", + "com.hk", + "edu.hk", + "gov.hk", + "idv.hk", + "net.hk", + "org.hk", + "\u516c\u53f8.hk", + "xn--55qx5d.hk", + "\u6559\u80b2.hk", + "xn--wcvs22d.hk", + "\u654e\u80b2.hk", + "xn--lcvr32d.hk", + "\u653f\u5e9c.hk", + "xn--mxtq1m.hk", + "\u500b\u4eba.hk", + "xn--gmqw5a.hk", + "\u4e2a\u4eba.hk", + "xn--ciqpn.hk", + "\u7b87\u4eba.hk", + "xn--gmq050i.hk", + "\u7db2\u7edc.hk", + "xn--zf0avx.hk", + "\u7f51\u7edc.hk", + "xn--io0a7i.hk", + "\u7ec4\u7e54.hk", + "xn--mk0axi.hk", + "\u7db2\u7d61.hk", + "xn--od0alg.hk", + "\u7f51\u7d61.hk", + "xn--od0aq3b.hk", + "\u7ec4\u7ec7.hk", + "xn--tn0ag.hk", + "\u7d44\u7e54.hk", + "xn--uc0atv.hk", + "\u7d44\u7ec7.hk", + "xn--uc0ay4a.hk", + "hm", + "hn", + "com.hn", + "edu.hn", + "org.hn", + "net.hn", + "mil.hn", + "gob.hn", + "hr", + "iz.hr", + "from.hr", + "name.hr", + "com.hr", + "ht", + "com.ht", + "shop.ht", + "firm.ht", + "info.ht", + "adult.ht", + "net.ht", + "pro.ht", + "org.ht", + "med.ht", + "art.ht", + "coop.ht", + "pol.ht", + "asso.ht", + "edu.ht", + "rel.ht", + "gouv.ht", + "perso.ht", + "hu", + "co.hu", + "info.hu", + "org.hu", + "priv.hu", + "sport.hu", + "tm.hu", + "2000.hu", + "agrar.hu", + "bolt.hu", + "casino.hu", + "city.hu", + "erotica.hu", + "erotika.hu", + "film.hu", + "forum.hu", + "games.hu", + "hotel.hu", + "ingatlan.hu", + "jogasz.hu", + "konyvelo.hu", + "lakas.hu", + "media.hu", + "news.hu", + "reklam.hu", + "sex.hu", + "shop.hu", + "suli.hu", + "szex.hu", + "tozsde.hu", + "utazas.hu", + "video.hu", + "id", + "ac.id", + "co.id", + "go.id", + "mil.id", + "net.id", + "or.id", + "sch.id", + "web.id", + "ie", + "gov.ie", + "im", + "co.im", + "ltd.co.im", + "plc.co.im", + "net.im", + "gov.im", + "org.im", + "nic.im", + "ac.im", + "in", + "co.in", + "firm.in", + "net.in", + "org.in", + "gen.in", + "ind.in", + "nic.in", + "ac.in", + "edu.in", + "res.in", + "gov.in", + "mil.in", + "info", + "int", + "eu.int", + "io", + "com.io", + "iq", + "gov.iq", + "edu.iq", + "mil.iq", + "com.iq", + "org.iq", + "net.iq", + "ir", + "ac.ir", + "co.ir", + "gov.ir", + "id.ir", + "net.ir", + "org.ir", + "sch.ir", + "\u0627\u06cc\u0631\u0627\u0646.ir", + "xn--mgba3a4f16a.ir", + "\u0627\u064a\u0631\u0627\u0646.ir", + "xn--mgba3a4fra.ir", + "is", + "net.is", + "com.is", + "edu.is", + "gov.is", + "org.is", + "int.is", + "it", + "gov.it", + "edu.it", + "agrigento.it", + "ag.it", + "alessandria.it", + "al.it", + "ancona.it", + "an.it", + "aosta.it", + "aoste.it", + "ao.it", + "arezzo.it", + "ar.it", + "ascoli-piceno.it", + "ascolipiceno.it", + "ap.it", + "asti.it", + "at.it", + "avellino.it", + "av.it", + "bari.it", + "ba.it", + "andria-barletta-trani.it", + "andriabarlettatrani.it", + "trani-barletta-andria.it", + "tranibarlettaandria.it", + "barletta-trani-andria.it", + "barlettatraniandria.it", + "andria-trani-barletta.it", + "andriatranibarletta.it", + "trani-andria-barletta.it", + "traniandriabarletta.it", + "bt.it", + "belluno.it", + "bl.it", + "benevento.it", + "bn.it", + "bergamo.it", + "bg.it", + "biella.it", + "bi.it", + "bologna.it", + "bo.it", + "bolzano.it", + "bozen.it", + "balsan.it", + "alto-adige.it", + "altoadige.it", + "suedtirol.it", + "bz.it", + "brescia.it", + "bs.it", + "brindisi.it", + "br.it", + "cagliari.it", + "ca.it", + "caltanissetta.it", + "cl.it", + "campobasso.it", + "cb.it", + "carboniaiglesias.it", + "carbonia-iglesias.it", + "iglesias-carbonia.it", + "iglesiascarbonia.it", + "ci.it", + "caserta.it", + "ce.it", + "catania.it", + "ct.it", + "catanzaro.it", + "cz.it", + "chieti.it", + "ch.it", + "como.it", + "co.it", + "cosenza.it", + "cs.it", + "cremona.it", + "cr.it", + "crotone.it", + "kr.it", + "cuneo.it", + "cn.it", + "dell-ogliastra.it", + "dellogliastra.it", + "ogliastra.it", + "og.it", + "enna.it", + "en.it", + "ferrara.it", + "fe.it", + "fermo.it", + "fm.it", + "firenze.it", + "florence.it", + "fi.it", + "foggia.it", + "fg.it", + "forli-cesena.it", + "forlicesena.it", + "cesena-forli.it", + "cesenaforli.it", + "fc.it", + "frosinone.it", + "fr.it", + "genova.it", + "genoa.it", + "ge.it", + "gorizia.it", + "go.it", + "grosseto.it", + "gr.it", + "imperia.it", + "im.it", + "isernia.it", + "is.it", + "laquila.it", + "aquila.it", + "aq.it", + "la-spezia.it", + "laspezia.it", + "sp.it", + "latina.it", + "lt.it", + "lecce.it", + "le.it", + "lecco.it", + "lc.it", + "livorno.it", + "li.it", + "lodi.it", + "lo.it", + "lucca.it", + "lu.it", + "macerata.it", + "mc.it", + "mantova.it", + "mn.it", + "massa-carrara.it", + "massacarrara.it", + "carrara-massa.it", + "carraramassa.it", + "ms.it", + "matera.it", + "mt.it", + "medio-campidano.it", + "mediocampidano.it", + "campidano-medio.it", + "campidanomedio.it", + "vs.it", + "messina.it", + "me.it", + "milano.it", + "milan.it", + "mi.it", + "modena.it", + "mo.it", + "monza.it", + "monza-brianza.it", + "monzabrianza.it", + "monzaebrianza.it", + "monzaedellabrianza.it", + "monza-e-della-brianza.it", + "mb.it", + "napoli.it", + "naples.it", + "na.it", + "novara.it", + "no.it", + "nuoro.it", + "nu.it", + "oristano.it", + "or.it", + "padova.it", + "padua.it", + "pd.it", + "palermo.it", + "pa.it", + "parma.it", + "pr.it", + "pavia.it", + "pv.it", + "perugia.it", + "pg.it", + "pescara.it", + "pe.it", + "pesaro-urbino.it", + "pesarourbino.it", + "urbino-pesaro.it", + "urbinopesaro.it", + "pu.it", + "piacenza.it", + "pc.it", + "pisa.it", + "pi.it", + "pistoia.it", + "pt.it", + "pordenone.it", + "pn.it", + "potenza.it", + "pz.it", + "prato.it", + "po.it", + "ragusa.it", + "rg.it", + "ravenna.it", + "ra.it", + "reggio-calabria.it", + "reggiocalabria.it", + "rc.it", + "reggio-emilia.it", + "reggioemilia.it", + "re.it", + "rieti.it", + "ri.it", + "rimini.it", + "rn.it", + "roma.it", + "rome.it", + "rm.it", + "rovigo.it", + "ro.it", + "salerno.it", + "sa.it", + "sassari.it", + "ss.it", + "savona.it", + "sv.it", + "siena.it", + "si.it", + "siracusa.it", + "sr.it", + "sondrio.it", + "so.it", + "taranto.it", + "ta.it", + "tempio-olbia.it", + "tempioolbia.it", + "olbia-tempio.it", + "olbiatempio.it", + "ot.it", + "teramo.it", + "te.it", + "terni.it", + "tr.it", + "torino.it", + "turin.it", + "to.it", + "trapani.it", + "tp.it", + "trento.it", + "trentino.it", + "tn.it", + "treviso.it", + "tv.it", + "trieste.it", + "ts.it", + "udine.it", + "ud.it", + "varese.it", + "va.it", + "venezia.it", + "venice.it", + "ve.it", + "verbania.it", + "vb.it", + "vercelli.it", + "vc.it", + "verona.it", + "vr.it", + "vibo-valentia.it", + "vibovalentia.it", + "vv.it", + "vicenza.it", + "vi.it", + "viterbo.it", + "vt.it", + "je", + "co.je", + "org.je", + "net.je", + "sch.je", + "gov.je", + "jo", + "com.jo", + "org.jo", + "net.jo", + "edu.jo", + "sch.jo", + "gov.jo", + "mil.jo", + "name.jo", + "jobs", + "jp", + "ac.jp", + "ad.jp", + "co.jp", + "ed.jp", + "go.jp", + "gr.jp", + "lg.jp", + "ne.jp", + "or.jp", + "kg", + "org.kg", + "net.kg", + "com.kg", + "edu.kg", + "gov.kg", + "mil.kg", + "ki", + "edu.ki", + "biz.ki", + "net.ki", + "org.ki", + "gov.ki", + "info.ki", + "com.ki", + "km", + "org.km", + "nom.km", + "gov.km", + "prd.km", + "tm.km", + "edu.km", + "mil.km", + "ass.km", + "com.km", + "coop.km", + "asso.km", + "presse.km", + "medecin.km", + "notaires.km", + "pharmaciens.km", + "veterinaire.km", + "gouv.km", + "kn", + "net.kn", + "org.kn", + "edu.kn", + "gov.kn", + "com.kp", + "edu.kp", + "gov.kp", + "org.kp", + "rep.kp", + "tra.kp", + "kr", + "ac.kr", + "co.kr", + "es.kr", + "go.kr", + "hs.kr", + "kg.kr", + "mil.kr", + "ms.kr", + "ne.kr", + "or.kr", + "pe.kr", + "re.kr", + "sc.kr", + "busan.kr", + "chungbuk.kr", + "chungnam.kr", + "daegu.kr", + "daejeon.kr", + "gangwon.kr", + "gwangju.kr", + "gyeongbuk.kr", + "gyeonggi.kr", + "gyeongnam.kr", + "incheon.kr", + "jeju.kr", + "jeonbuk.kr", + "jeonnam.kr", + "seoul.kr", + "ulsan.kr", + "ky", + "edu.ky", + "gov.ky", + "com.ky", + "org.ky", + "net.ky", + "kz", + "org.kz", + "edu.kz", + "net.kz", + "gov.kz", + "mil.kz", + "com.kz", + "la", + "int.la", + "net.la", + "info.la", + "edu.la", + "gov.la", + "per.la", + "com.la", + "org.la", + "com.lb", + "edu.lb", + "gov.lb", + "net.lb", + "org.lb", + "lc", + "com.lc", + "net.lc", + "co.lc", + "org.lc", + "edu.lc", + "gov.lc", + "li", + "lk", + "gov.lk", + "sch.lk", + "net.lk", + "int.lk", + "com.lk", + "org.lk", + "edu.lk", + "ngo.lk", + "soc.lk", + "web.lk", + "ltd.lk", + "assn.lk", + "grp.lk", + "hotel.lk", + "com.lr", + "edu.lr", + "gov.lr", + "org.lr", + "net.lr", + "ls", + "co.ls", + "org.ls", + "lt", + "gov.lt", + "lu", + "lv", + "com.lv", + "edu.lv", + "gov.lv", + "org.lv", + "mil.lv", + "id.lv", + "net.lv", + "asn.lv", + "conf.lv", + "ly", + "com.ly", + "net.ly", + "gov.ly", + "plc.ly", + "edu.ly", + "sch.ly", + "med.ly", + "org.ly", + "id.ly", + "ma", + "co.ma", + "net.ma", + "gov.ma", + "org.ma", + "ac.ma", + "press.ma", + "mc", + "tm.mc", + "asso.mc", + "md", + "me", + "co.me", + "net.me", + "org.me", + "edu.me", + "ac.me", + "gov.me", + "its.me", + "priv.me", + "mg", + "org.mg", + "nom.mg", + "gov.mg", + "prd.mg", + "tm.mg", + "edu.mg", + "mil.mg", + "com.mg", + "mh", + "mil", + "mk", + "com.mk", + "org.mk", + "net.mk", + "edu.mk", + "gov.mk", + "inf.mk", + "name.mk", + "ml", + "com.ml", + "edu.ml", + "gouv.ml", + "gov.ml", + "net.ml", + "org.ml", + "presse.ml", + "mn", + "gov.mn", + "edu.mn", + "org.mn", + "mo", + "com.mo", + "net.mo", + "org.mo", + "edu.mo", + "gov.mo", + "mobi", + "mp", + "mq", + "mr", + "gov.mr", + "ms", + "mu", + "com.mu", + "net.mu", + "org.mu", + "gov.mu", + "ac.mu", + "co.mu", + "or.mu", + "museum", + "academy.museum", + "agriculture.museum", + "air.museum", + "airguard.museum", + "alabama.museum", + "alaska.museum", + "amber.museum", + "ambulance.museum", + "american.museum", + "americana.museum", + "americanantiques.museum", + "americanart.museum", + "amsterdam.museum", + "and.museum", + "annefrank.museum", + "anthro.museum", + "anthropology.museum", + "antiques.museum", + "aquarium.museum", + "arboretum.museum", + "archaeological.museum", + "archaeology.museum", + "architecture.museum", + "art.museum", + "artanddesign.museum", + "artcenter.museum", + "artdeco.museum", + "arteducation.museum", + "artgallery.museum", + "arts.museum", + "artsandcrafts.museum", + "asmatart.museum", + "assassination.museum", + "assisi.museum", + "association.museum", + "astronomy.museum", + "atlanta.museum", + "austin.museum", + "australia.museum", + "automotive.museum", + "aviation.museum", + "axis.museum", + "badajoz.museum", + "baghdad.museum", + "bahn.museum", + "bale.museum", + "baltimore.museum", + "barcelona.museum", + "baseball.museum", + "basel.museum", + "baths.museum", + "bauern.museum", + "beauxarts.museum", + "beeldengeluid.museum", + "bellevue.museum", + "bergbau.museum", + "berkeley.museum", + "berlin.museum", + "bern.museum", + "bible.museum", + "bilbao.museum", + "bill.museum", + "birdart.museum", + "birthplace.museum", + "bonn.museum", + "boston.museum", + "botanical.museum", + "botanicalgarden.museum", + "botanicgarden.museum", + "botany.museum", + "brandywinevalley.museum", + "brasil.museum", + "bristol.museum", + "british.museum", + "britishcolumbia.museum", + "broadcast.museum", + "brunel.museum", + "brussel.museum", + "brussels.museum", + "bruxelles.museum", + "building.museum", + "burghof.museum", + "bus.museum", + "bushey.museum", + "cadaques.museum", + "california.museum", + "cambridge.museum", + "can.museum", + "canada.museum", + "capebreton.museum", + "carrier.museum", + "cartoonart.museum", + "casadelamoneda.museum", + "castle.museum", + "castres.museum", + "celtic.museum", + "center.museum", + "chattanooga.museum", + "cheltenham.museum", + "chesapeakebay.museum", + "chicago.museum", + "children.museum", + "childrens.museum", + "childrensgarden.museum", + "chiropractic.museum", + "chocolate.museum", + "christiansburg.museum", + "cincinnati.museum", + "cinema.museum", + "circus.museum", + "civilisation.museum", + "civilization.museum", + "civilwar.museum", + "clinton.museum", + "clock.museum", + "coal.museum", + "coastaldefence.museum", + "cody.museum", + "coldwar.museum", + "collection.museum", + "colonialwilliamsburg.museum", + "coloradoplateau.museum", + "columbia.museum", + "columbus.museum", + "communication.museum", + "communications.museum", + "community.museum", + "computer.museum", + "computerhistory.museum", + "comunica\u00e7\u00f5es.museum", + "xn--comunicaes-v6a2o.museum", + "contemporary.museum", + "contemporaryart.museum", + "convent.museum", + "copenhagen.museum", + "corporation.museum", + "correios-e-telecomunica\u00e7\u00f5es.museum", + "xn--correios-e-telecomunicaes-ghc29a.museum", + "corvette.museum", + "costume.museum", + "countryestate.museum", + "county.museum", + "crafts.museum", + "cranbrook.museum", + "creation.museum", + "cultural.museum", + "culturalcenter.museum", + "culture.museum", + "cyber.museum", + "cymru.museum", + "dali.museum", + "dallas.museum", + "database.museum", + "ddr.museum", + "decorativearts.museum", + "delaware.museum", + "delmenhorst.museum", + "denmark.museum", + "depot.museum", + "design.museum", + "detroit.museum", + "dinosaur.museum", + "discovery.museum", + "dolls.museum", + "donostia.museum", + "durham.museum", + "eastafrica.museum", + "eastcoast.museum", + "education.museum", + "educational.museum", + "egyptian.museum", + "eisenbahn.museum", + "elburg.museum", + "elvendrell.museum", + "embroidery.museum", + "encyclopedic.museum", + "england.museum", + "entomology.museum", + "environment.museum", + "environmentalconservation.museum", + "epilepsy.museum", + "essex.museum", + "estate.museum", + "ethnology.museum", + "exeter.museum", + "exhibition.museum", + "family.museum", + "farm.museum", + "farmequipment.museum", + "farmers.museum", + "farmstead.museum", + "field.museum", + "figueres.museum", + "filatelia.museum", + "film.museum", + "fineart.museum", + "finearts.museum", + "finland.museum", + "flanders.museum", + "florida.museum", + "force.museum", + "fortmissoula.museum", + "fortworth.museum", + "foundation.museum", + "francaise.museum", + "frankfurt.museum", + "franziskaner.museum", + "freemasonry.museum", + "freiburg.museum", + "fribourg.museum", + "frog.museum", + "fundacio.museum", + "furniture.museum", + "gallery.museum", + "garden.museum", + "gateway.museum", + "geelvinck.museum", + "gemological.museum", + "geology.museum", + "georgia.museum", + "giessen.museum", + "glas.museum", + "glass.museum", + "gorge.museum", + "grandrapids.museum", + "graz.museum", + "guernsey.museum", + "halloffame.museum", + "hamburg.museum", + "handson.museum", + "harvestcelebration.museum", + "hawaii.museum", + "health.museum", + "heimatunduhren.museum", + "hellas.museum", + "helsinki.museum", + "hembygdsforbund.museum", + "heritage.museum", + "histoire.museum", + "historical.museum", + "historicalsociety.museum", + "historichouses.museum", + "historisch.museum", + "historisches.museum", + "history.museum", + "historyofscience.museum", + "horology.museum", + "house.museum", + "humanities.museum", + "illustration.museum", + "imageandsound.museum", + "indian.museum", + "indiana.museum", + "indianapolis.museum", + "indianmarket.museum", + "intelligence.museum", + "interactive.museum", + "iraq.museum", + "iron.museum", + "isleofman.museum", + "jamison.museum", + "jefferson.museum", + "jerusalem.museum", + "jewelry.museum", + "jewish.museum", + "jewishart.museum", + "jfk.museum", + "journalism.museum", + "judaica.museum", + "judygarland.museum", + "juedisches.museum", + "juif.museum", + "karate.museum", + "karikatur.museum", + "kids.museum", + "koebenhavn.museum", + "koeln.museum", + "kunst.museum", + "kunstsammlung.museum", + "kunstunddesign.museum", + "labor.museum", + "labour.museum", + "lajolla.museum", + "lancashire.museum", + "landes.museum", + "lans.museum", + "l\u00e4ns.museum", + "xn--lns-qla.museum", + "larsson.museum", + "lewismiller.museum", + "lincoln.museum", + "linz.museum", + "living.museum", + "livinghistory.museum", + "localhistory.museum", + "london.museum", + "losangeles.museum", + "louvre.museum", + "loyalist.museum", + "lucerne.museum", + "luxembourg.museum", + "luzern.museum", + "mad.museum", + "madrid.museum", + "mallorca.museum", + "manchester.museum", + "mansion.museum", + "mansions.museum", + "manx.museum", + "marburg.museum", + "maritime.museum", + "maritimo.museum", + "maryland.museum", + "marylhurst.museum", + "media.museum", + "medical.museum", + "medizinhistorisches.museum", + "meeres.museum", + "memorial.museum", + "mesaverde.museum", + "michigan.museum", + "midatlantic.museum", + "military.museum", + "mill.museum", + "miners.museum", + "mining.museum", + "minnesota.museum", + "missile.museum", + "missoula.museum", + "modern.museum", + "moma.museum", + "money.museum", + "monmouth.museum", + "monticello.museum", + "montreal.museum", + "moscow.museum", + "motorcycle.museum", + "muenchen.museum", + "muenster.museum", + "mulhouse.museum", + "muncie.museum", + "museet.museum", + "museumcenter.museum", + "museumvereniging.museum", + "music.museum", + "national.museum", + "nationalfirearms.museum", + "nationalheritage.museum", + "nativeamerican.museum", + "naturalhistory.museum", + "naturalhistorymuseum.museum", + "naturalsciences.museum", + "nature.museum", + "naturhistorisches.museum", + "natuurwetenschappen.museum", + "naumburg.museum", + "naval.museum", + "nebraska.museum", + "neues.museum", + "newhampshire.museum", + "newjersey.museum", + "newmexico.museum", + "newport.museum", + "newspaper.museum", + "newyork.museum", + "niepce.museum", + "norfolk.museum", + "north.museum", + "nrw.museum", + "nuernberg.museum", + "nuremberg.museum", + "nyc.museum", + "nyny.museum", + "oceanographic.museum", + "oceanographique.museum", + "omaha.museum", + "online.museum", + "ontario.museum", + "openair.museum", + "oregon.museum", + "oregontrail.museum", + "otago.museum", + "oxford.museum", + "pacific.museum", + "paderborn.museum", + "palace.museum", + "paleo.museum", + "palmsprings.museum", + "panama.museum", + "paris.museum", + "pasadena.museum", + "pharmacy.museum", + "philadelphia.museum", + "philadelphiaarea.museum", + "philately.museum", + "phoenix.museum", + "photography.museum", + "pilots.museum", + "pittsburgh.museum", + "planetarium.museum", + "plantation.museum", + "plants.museum", + "plaza.museum", + "portal.museum", + "portland.museum", + "portlligat.museum", + "posts-and-telecommunications.museum", + "preservation.museum", + "presidio.museum", + "press.museum", + "project.museum", + "public.museum", + "pubol.museum", + "quebec.museum", + "railroad.museum", + "railway.museum", + "research.museum", + "resistance.museum", + "riodejaneiro.museum", + "rochester.museum", + "rockart.museum", + "roma.museum", + "russia.museum", + "saintlouis.museum", + "salem.museum", + "salvadordali.museum", + "salzburg.museum", + "sandiego.museum", + "sanfrancisco.museum", + "santabarbara.museum", + "santacruz.museum", + "santafe.museum", + "saskatchewan.museum", + "satx.museum", + "savannahga.museum", + "schlesisches.museum", + "schoenbrunn.museum", + "schokoladen.museum", + "school.museum", + "schweiz.museum", + "science.museum", + "scienceandhistory.museum", + "scienceandindustry.museum", + "sciencecenter.museum", + "sciencecenters.museum", + "science-fiction.museum", + "sciencehistory.museum", + "sciences.museum", + "sciencesnaturelles.museum", + "scotland.museum", + "seaport.museum", + "settlement.museum", + "settlers.museum", + "shell.museum", + "sherbrooke.museum", + "sibenik.museum", + "silk.museum", + "ski.museum", + "skole.museum", + "society.museum", + "sologne.museum", + "soundandvision.museum", + "southcarolina.museum", + "southwest.museum", + "space.museum", + "spy.museum", + "square.museum", + "stadt.museum", + "stalbans.museum", + "starnberg.museum", + "state.museum", + "stateofdelaware.museum", + "station.museum", + "steam.museum", + "steiermark.museum", + "stjohn.museum", + "stockholm.museum", + "stpetersburg.museum", + "stuttgart.museum", + "suisse.museum", + "surgeonshall.museum", + "surrey.museum", + "svizzera.museum", + "sweden.museum", + "sydney.museum", + "tank.museum", + "tcm.museum", + "technology.museum", + "telekommunikation.museum", + "television.museum", + "texas.museum", + "textile.museum", + "theater.museum", + "time.museum", + "timekeeping.museum", + "topology.museum", + "torino.museum", + "touch.museum", + "town.museum", + "transport.museum", + "tree.museum", + "trolley.museum", + "trust.museum", + "trustee.museum", + "uhren.museum", + "ulm.museum", + "undersea.museum", + "university.museum", + "usa.museum", + "usantiques.museum", + "usarts.museum", + "uscountryestate.museum", + "usculture.museum", + "usdecorativearts.museum", + "usgarden.museum", + "ushistory.museum", + "ushuaia.museum", + "uslivinghistory.museum", + "utah.museum", + "uvic.museum", + "valley.museum", + "vantaa.museum", + "versailles.museum", + "viking.museum", + "village.museum", + "virginia.museum", + "virtual.museum", + "virtuel.museum", + "vlaanderen.museum", + "volkenkunde.museum", + "wales.museum", + "wallonie.museum", + "war.museum", + "washingtondc.museum", + "watchandclock.museum", + "watch-and-clock.museum", + "western.museum", + "westfalen.museum", + "whaling.museum", + "wildlife.museum", + "williamsburg.museum", + "windmill.museum", + "workshop.museum", + "york.museum", + "yorkshire.museum", + "yosemite.museum", + "youth.museum", + "zoological.museum", + "zoology.museum", + "\u05d9\u05e8\u05d5\u05e9\u05dc\u05d9\u05dd.museum", + "xn--9dbhblg6di.museum", + "\u0438\u043a\u043e\u043c.museum", + "xn--h1aegh.museum", + "mv", + "aero.mv", + "biz.mv", + "com.mv", + "coop.mv", + "edu.mv", + "gov.mv", + "info.mv", + "int.mv", + "mil.mv", + "museum.mv", + "name.mv", + "net.mv", + "org.mv", + "pro.mv", + "mw", + "ac.mw", + "biz.mw", + "co.mw", + "com.mw", + "coop.mw", + "edu.mw", + "gov.mw", + "int.mw", + "museum.mw", + "net.mw", + "org.mw", + "mx", + "com.mx", + "org.mx", + "gob.mx", + "edu.mx", + "net.mx", + "my", + "com.my", + "net.my", + "org.my", + "gov.my", + "edu.my", + "mil.my", + "name.my", + "na", + "info.na", + "pro.na", + "name.na", + "school.na", + "or.na", + "dr.na", + "us.na", + "mx.na", + "ca.na", + "in.na", + "cc.na", + "tv.na", + "ws.na", + "mobi.na", + "co.na", + "com.na", + "org.na", + "name", + "nc", + "asso.nc", + "ne", + "net", + "nf", + "com.nf", + "net.nf", + "per.nf", + "rec.nf", + "web.nf", + "arts.nf", + "firm.nf", + "info.nf", + "other.nf", + "store.nf", + "ac.ng", + "com.ng", + "edu.ng", + "gov.ng", + "net.ng", + "org.ng", + "nl", + "bv.nl", + "no", + "fhs.no", + "vgs.no", + "fylkesbibl.no", + "folkebibl.no", + "museum.no", + "idrett.no", + "priv.no", + "mil.no", + "stat.no", + "dep.no", + "kommune.no", + "herad.no", + "aa.no", + "ah.no", + "bu.no", + "fm.no", + "hl.no", + "hm.no", + "jan-mayen.no", + "mr.no", + "nl.no", + "nt.no", + "of.no", + "ol.no", + "oslo.no", + "rl.no", + "sf.no", + "st.no", + "svalbard.no", + "tm.no", + "tr.no", + "va.no", + "vf.no", + "gs.aa.no", + "gs.ah.no", + "gs.bu.no", + "gs.fm.no", + "gs.hl.no", + "gs.hm.no", + "gs.jan-mayen.no", + "gs.mr.no", + "gs.nl.no", + "gs.nt.no", + "gs.of.no", + "gs.ol.no", + "gs.oslo.no", + "gs.rl.no", + "gs.sf.no", + "gs.st.no", + "gs.svalbard.no", + "gs.tm.no", + "gs.tr.no", + "gs.va.no", + "gs.vf.no", + "akrehamn.no", + "\u00e5krehamn.no", + "xn--krehamn-dxa.no", + "algard.no", + "\u00e5lg\u00e5rd.no", + "xn--lgrd-poac.no", + "arna.no", + "brumunddal.no", + "bryne.no", + "bronnoysund.no", + "br\u00f8nn\u00f8ysund.no", + "xn--brnnysund-m8ac.no", + "drobak.no", + "dr\u00f8bak.no", + "xn--drbak-wua.no", + "egersund.no", + "fetsund.no", + "floro.no", + "flor\u00f8.no", + "xn--flor-jra.no", + "fredrikstad.no", + "hokksund.no", + "honefoss.no", + "h\u00f8nefoss.no", + "xn--hnefoss-q1a.no", + "jessheim.no", + "jorpeland.no", + "j\u00f8rpeland.no", + "xn--jrpeland-54a.no", + "kirkenes.no", + "kopervik.no", + "krokstadelva.no", + "langevag.no", + "langev\u00e5g.no", + "xn--langevg-jxa.no", + "leirvik.no", + "mjondalen.no", + "mj\u00f8ndalen.no", + "xn--mjndalen-64a.no", + "mo-i-rana.no", + "mosjoen.no", + "mosj\u00f8en.no", + "xn--mosjen-eya.no", + "nesoddtangen.no", + "orkanger.no", + "osoyro.no", + "os\u00f8yro.no", + "xn--osyro-wua.no", + "raholt.no", + "r\u00e5holt.no", + "xn--rholt-mra.no", + "sandnessjoen.no", + "sandnessj\u00f8en.no", + "xn--sandnessjen-ogb.no", + "skedsmokorset.no", + "slattum.no", + "spjelkavik.no", + "stathelle.no", + "stavern.no", + "stjordalshalsen.no", + "stj\u00f8rdalshalsen.no", + "xn--stjrdalshalsen-sqb.no", + "tananger.no", + "tranby.no", + "vossevangen.no", + "afjord.no", + "\u00e5fjord.no", + "xn--fjord-lra.no", + "agdenes.no", + "al.no", + "\u00e5l.no", + "xn--l-1fa.no", + "alesund.no", + "\u00e5lesund.no", + "xn--lesund-hua.no", + "alstahaug.no", + "alta.no", + "\u00e1lt\u00e1.no", + "xn--lt-liac.no", + "alaheadju.no", + "\u00e1laheadju.no", + "xn--laheadju-7ya.no", + "alvdal.no", + "amli.no", + "\u00e5mli.no", + "xn--mli-tla.no", + "amot.no", + "\u00e5mot.no", + "xn--mot-tla.no", + "andebu.no", + "andoy.no", + "and\u00f8y.no", + "xn--andy-ira.no", + "andasuolo.no", + "ardal.no", + "\u00e5rdal.no", + "xn--rdal-poa.no", + "aremark.no", + "arendal.no", + "\u00e5s.no", + "xn--s-1fa.no", + "aseral.no", + "\u00e5seral.no", + "xn--seral-lra.no", + "asker.no", + "askim.no", + "askvoll.no", + "askoy.no", + "ask\u00f8y.no", + "xn--asky-ira.no", + "asnes.no", + "\u00e5snes.no", + "xn--snes-poa.no", + "audnedaln.no", + "aukra.no", + "aure.no", + "aurland.no", + "aurskog-holand.no", + "aurskog-h\u00f8land.no", + "xn--aurskog-hland-jnb.no", + "austevoll.no", + "austrheim.no", + "averoy.no", + "aver\u00f8y.no", + "xn--avery-yua.no", + "balestrand.no", + "ballangen.no", + "balat.no", + "b\u00e1l\u00e1t.no", + "xn--blt-elab.no", + "balsfjord.no", + "bahccavuotna.no", + "b\u00e1hccavuotna.no", + "xn--bhccavuotna-k7a.no", + "bamble.no", + "bardu.no", + "beardu.no", + "beiarn.no", + "bajddar.no", + "b\u00e1jddar.no", + "xn--bjddar-pta.no", + "baidar.no", + "b\u00e1id\u00e1r.no", + "xn--bidr-5nac.no", + "berg.no", + "bergen.no", + "berlevag.no", + "berlev\u00e5g.no", + "xn--berlevg-jxa.no", + "bearalvahki.no", + "bearalv\u00e1hki.no", + "xn--bearalvhki-y4a.no", + "bindal.no", + "birkenes.no", + "bjarkoy.no", + "bjark\u00f8y.no", + "xn--bjarky-fya.no", + "bjerkreim.no", + "bjugn.no", + "bodo.no", + "bod\u00f8.no", + "xn--bod-2na.no", + "badaddja.no", + "b\u00e5d\u00e5ddj\u00e5.no", + "xn--bdddj-mrabd.no", + "budejju.no", + "bokn.no", + "bremanger.no", + "bronnoy.no", + "br\u00f8nn\u00f8y.no", + "xn--brnny-wuac.no", + "bygland.no", + "bykle.no", + "barum.no", + "b\u00e6rum.no", + "xn--brum-voa.no", + "bo.telemark.no", + "b\u00f8.telemark.no", + "xn--b-5ga.telemark.no", + "bo.nordland.no", + "b\u00f8.nordland.no", + "xn--b-5ga.nordland.no", + "bievat.no", + "biev\u00e1t.no", + "xn--bievt-0qa.no", + "bomlo.no", + "b\u00f8mlo.no", + "xn--bmlo-gra.no", + "batsfjord.no", + "b\u00e5tsfjord.no", + "xn--btsfjord-9za.no", + "bahcavuotna.no", + "b\u00e1hcavuotna.no", + "xn--bhcavuotna-s4a.no", + "dovre.no", + "drammen.no", + "drangedal.no", + "dyroy.no", + "dyr\u00f8y.no", + "xn--dyry-ira.no", + "donna.no", + "d\u00f8nna.no", + "xn--dnna-gra.no", + "eid.no", + "eidfjord.no", + "eidsberg.no", + "eidskog.no", + "eidsvoll.no", + "eigersund.no", + "elverum.no", + "enebakk.no", + "engerdal.no", + "etne.no", + "etnedal.no", + "evenes.no", + "evenassi.no", + "even\u00e1\u0161\u0161i.no", + "xn--eveni-0qa01ga.no", + "evje-og-hornnes.no", + "farsund.no", + "fauske.no", + "fuossko.no", + "fuoisku.no", + "fedje.no", + "fet.no", + "finnoy.no", + "finn\u00f8y.no", + "xn--finny-yua.no", + "fitjar.no", + "fjaler.no", + "fjell.no", + "flakstad.no", + "flatanger.no", + "flekkefjord.no", + "flesberg.no", + "flora.no", + "fla.no", + "fl\u00e5.no", + "xn--fl-zia.no", + "folldal.no", + "forsand.no", + "fosnes.no", + "frei.no", + "frogn.no", + "froland.no", + "frosta.no", + "frana.no", + "fr\u00e6na.no", + "xn--frna-woa.no", + "froya.no", + "fr\u00f8ya.no", + "xn--frya-hra.no", + "fusa.no", + "fyresdal.no", + "forde.no", + "f\u00f8rde.no", + "xn--frde-gra.no", + "gamvik.no", + "gangaviika.no", + "g\u00e1\u014bgaviika.no", + "xn--ggaviika-8ya47h.no", + "gaular.no", + "gausdal.no", + "gildeskal.no", + "gildesk\u00e5l.no", + "xn--gildeskl-g0a.no", + "giske.no", + "gjemnes.no", + "gjerdrum.no", + "gjerstad.no", + "gjesdal.no", + "gjovik.no", + "gj\u00f8vik.no", + "xn--gjvik-wua.no", + "gloppen.no", + "gol.no", + "gran.no", + "grane.no", + "granvin.no", + "gratangen.no", + "grimstad.no", + "grong.no", + "kraanghke.no", + "kr\u00e5anghke.no", + "xn--kranghke-b0a.no", + "grue.no", + "gulen.no", + "hadsel.no", + "halden.no", + "halsa.no", + "hamar.no", + "hamaroy.no", + "habmer.no", + "h\u00e1bmer.no", + "xn--hbmer-xqa.no", + "hapmir.no", + "h\u00e1pmir.no", + "xn--hpmir-xqa.no", + "hammerfest.no", + "hammarfeasta.no", + "h\u00e1mm\u00e1rfeasta.no", + "xn--hmmrfeasta-s4ac.no", + "haram.no", + "hareid.no", + "harstad.no", + "hasvik.no", + "aknoluokta.no", + "\u00e1k\u014boluokta.no", + "xn--koluokta-7ya57h.no", + "hattfjelldal.no", + "aarborte.no", + "haugesund.no", + "hemne.no", + "hemnes.no", + "hemsedal.no", + "heroy.more-og-romsdal.no", + "her\u00f8y.m\u00f8re-og-romsdal.no", + "xn--hery-ira.xn--mre-og-romsdal-qqb.no", + "heroy.nordland.no", + "her\u00f8y.nordland.no", + "xn--hery-ira.nordland.no", + "hitra.no", + "hjartdal.no", + "hjelmeland.no", + "hobol.no", + "hob\u00f8l.no", + "xn--hobl-ira.no", + "hof.no", + "hol.no", + "hole.no", + "holmestrand.no", + "holtalen.no", + "holt\u00e5len.no", + "xn--holtlen-hxa.no", + "hornindal.no", + "horten.no", + "hurdal.no", + "hurum.no", + "hvaler.no", + "hyllestad.no", + "hagebostad.no", + "h\u00e6gebostad.no", + "xn--hgebostad-g3a.no", + "hoyanger.no", + "h\u00f8yanger.no", + "xn--hyanger-q1a.no", + "hoylandet.no", + "h\u00f8ylandet.no", + "xn--hylandet-54a.no", + "ha.no", + "h\u00e5.no", + "xn--h-2fa.no", + "ibestad.no", + "inderoy.no", + "inder\u00f8y.no", + "xn--indery-fya.no", + "iveland.no", + "jevnaker.no", + "jondal.no", + "jolster.no", + "j\u00f8lster.no", + "xn--jlster-bya.no", + "karasjok.no", + "karasjohka.no", + "k\u00e1r\u00e1\u0161johka.no", + "xn--krjohka-hwab49j.no", + "karlsoy.no", + "galsa.no", + "g\u00e1ls\u00e1.no", + "xn--gls-elac.no", + "karmoy.no", + "karm\u00f8y.no", + "xn--karmy-yua.no", + "kautokeino.no", + "guovdageaidnu.no", + "klepp.no", + "klabu.no", + "kl\u00e6bu.no", + "xn--klbu-woa.no", + "kongsberg.no", + "kongsvinger.no", + "kragero.no", + "krager\u00f8.no", + "xn--krager-gya.no", + "kristiansand.no", + "kristiansund.no", + "krodsherad.no", + "kr\u00f8dsherad.no", + "xn--krdsherad-m8a.no", + "kvalsund.no", + "rahkkeravju.no", + "r\u00e1hkker\u00e1vju.no", + "xn--rhkkervju-01af.no", + "kvam.no", + "kvinesdal.no", + "kvinnherad.no", + "kviteseid.no", + "kvitsoy.no", + "kvits\u00f8y.no", + "xn--kvitsy-fya.no", + "kvafjord.no", + "kv\u00e6fjord.no", + "xn--kvfjord-nxa.no", + "giehtavuoatna.no", + "kvanangen.no", + "kv\u00e6nangen.no", + "xn--kvnangen-k0a.no", + "navuotna.no", + "n\u00e1vuotna.no", + "xn--nvuotna-hwa.no", + "kafjord.no", + "k\u00e5fjord.no", + "xn--kfjord-iua.no", + "gaivuotna.no", + "g\u00e1ivuotna.no", + "xn--givuotna-8ya.no", + "larvik.no", + "lavangen.no", + "lavagis.no", + "loabat.no", + "loab\u00e1t.no", + "xn--loabt-0qa.no", + "lebesby.no", + "davvesiida.no", + "leikanger.no", + "leirfjord.no", + "leka.no", + "leksvik.no", + "lenvik.no", + "leangaviika.no", + "lea\u014bgaviika.no", + "xn--leagaviika-52b.no", + "lesja.no", + "levanger.no", + "lier.no", + "lierne.no", + "lillehammer.no", + "lillesand.no", + "lindesnes.no", + "lindas.no", + "lind\u00e5s.no", + "xn--linds-pra.no", + "lom.no", + "loppa.no", + "lahppi.no", + "l\u00e1hppi.no", + "xn--lhppi-xqa.no", + "lund.no", + "lunner.no", + "luroy.no", + "lur\u00f8y.no", + "xn--lury-ira.no", + "luster.no", + "lyngdal.no", + "lyngen.no", + "ivgu.no", + "lardal.no", + "lerdal.no", + "l\u00e6rdal.no", + "xn--lrdal-sra.no", + "lodingen.no", + "l\u00f8dingen.no", + "xn--ldingen-q1a.no", + "lorenskog.no", + "l\u00f8renskog.no", + "xn--lrenskog-54a.no", + "loten.no", + "l\u00f8ten.no", + "xn--lten-gra.no", + "malvik.no", + "masoy.no", + "m\u00e5s\u00f8y.no", + "xn--msy-ula0h.no", + "muosat.no", + "muos\u00e1t.no", + "xn--muost-0qa.no", + "mandal.no", + "marker.no", + "marnardal.no", + "masfjorden.no", + "meland.no", + "meldal.no", + "melhus.no", + "meloy.no", + "mel\u00f8y.no", + "xn--mely-ira.no", + "meraker.no", + "mer\u00e5ker.no", + "xn--merker-kua.no", + "moareke.no", + "mo\u00e5reke.no", + "xn--moreke-jua.no", + "midsund.no", + "midtre-gauldal.no", + "modalen.no", + "modum.no", + "molde.no", + "moskenes.no", + "moss.no", + "mosvik.no", + "malselv.no", + "m\u00e5lselv.no", + "xn--mlselv-iua.no", + "malatvuopmi.no", + "m\u00e1latvuopmi.no", + "xn--mlatvuopmi-s4a.no", + "namdalseid.no", + "aejrie.no", + "namsos.no", + "namsskogan.no", + "naamesjevuemie.no", + "n\u00e5\u00e5mesjevuemie.no", + "xn--nmesjevuemie-tcba.no", + "laakesvuemie.no", + "nannestad.no", + "narvik.no", + "narviika.no", + "naustdal.no", + "nedre-eiker.no", + "nes.akershus.no", + "nes.buskerud.no", + "nesna.no", + "nesodden.no", + "nesseby.no", + "unjarga.no", + "unj\u00e1rga.no", + "xn--unjrga-rta.no", + "nesset.no", + "nissedal.no", + "nittedal.no", + "nord-aurdal.no", + "nord-fron.no", + "nord-odal.no", + "norddal.no", + "nordkapp.no", + "davvenjarga.no", + "davvenj\u00e1rga.no", + "xn--davvenjrga-y4a.no", + "nordre-land.no", + "nordreisa.no", + "raisa.no", + "r\u00e1isa.no", + "xn--risa-5na.no", + "nore-og-uvdal.no", + "notodden.no", + "naroy.no", + "n\u00e6r\u00f8y.no", + "xn--nry-yla5g.no", + "notteroy.no", + "n\u00f8tter\u00f8y.no", + "xn--nttery-byae.no", + "odda.no", + "oksnes.no", + "\u00f8ksnes.no", + "xn--ksnes-uua.no", + "oppdal.no", + "oppegard.no", + "oppeg\u00e5rd.no", + "xn--oppegrd-ixa.no", + "orkdal.no", + "orland.no", + "\u00f8rland.no", + "xn--rland-uua.no", + "orskog.no", + "\u00f8rskog.no", + "xn--rskog-uua.no", + "orsta.no", + "\u00f8rsta.no", + "xn--rsta-fra.no", + "os.hedmark.no", + "os.hordaland.no", + "osen.no", + "osteroy.no", + "oster\u00f8y.no", + "xn--ostery-fya.no", + "ostre-toten.no", + "\u00f8stre-toten.no", + "xn--stre-toten-zcb.no", + "overhalla.no", + "ovre-eiker.no", + "\u00f8vre-eiker.no", + "xn--vre-eiker-k8a.no", + "oyer.no", + "\u00f8yer.no", + "xn--yer-zna.no", + "oygarden.no", + "\u00f8ygarden.no", + "xn--ygarden-p1a.no", + "oystre-slidre.no", + "\u00f8ystre-slidre.no", + "xn--ystre-slidre-ujb.no", + "porsanger.no", + "porsangu.no", + "pors\u00e1\u014bgu.no", + "xn--porsgu-sta26f.no", + "porsgrunn.no", + "radoy.no", + "rad\u00f8y.no", + "xn--rady-ira.no", + "rakkestad.no", + "rana.no", + "ruovat.no", + "randaberg.no", + "rauma.no", + "rendalen.no", + "rennebu.no", + "rennesoy.no", + "rennes\u00f8y.no", + "xn--rennesy-v1a.no", + "rindal.no", + "ringebu.no", + "ringerike.no", + "ringsaker.no", + "rissa.no", + "risor.no", + "ris\u00f8r.no", + "xn--risr-ira.no", + "roan.no", + "rollag.no", + "rygge.no", + "ralingen.no", + "r\u00e6lingen.no", + "xn--rlingen-mxa.no", + "rodoy.no", + "r\u00f8d\u00f8y.no", + "xn--rdy-0nab.no", + "romskog.no", + "r\u00f8mskog.no", + "xn--rmskog-bya.no", + "roros.no", + "r\u00f8ros.no", + "xn--rros-gra.no", + "rost.no", + "r\u00f8st.no", + "xn--rst-0na.no", + "royken.no", + "r\u00f8yken.no", + "xn--ryken-vua.no", + "royrvik.no", + "r\u00f8yrvik.no", + "xn--ryrvik-bya.no", + "rade.no", + "r\u00e5de.no", + "xn--rde-ula.no", + "salangen.no", + "siellak.no", + "saltdal.no", + "salat.no", + "s\u00e1l\u00e1t.no", + "xn--slt-elab.no", + "s\u00e1lat.no", + "xn--slat-5na.no", + "samnanger.no", + "sande.more-og-romsdal.no", + "sande.m\u00f8re-og-romsdal.no", + "sande.xn--mre-og-romsdal-qqb.no", + "sande.vestfold.no", + "sandefjord.no", + "sandnes.no", + "sandoy.no", + "sand\u00f8y.no", + "xn--sandy-yua.no", + "sarpsborg.no", + "sauda.no", + "sauherad.no", + "sel.no", + "selbu.no", + "selje.no", + "seljord.no", + "sigdal.no", + "siljan.no", + "sirdal.no", + "skaun.no", + "skedsmo.no", + "ski.no", + "skien.no", + "skiptvet.no", + "skjervoy.no", + "skjerv\u00f8y.no", + "xn--skjervy-v1a.no", + "skierva.no", + "skierv\u00e1.no", + "xn--skierv-uta.no", + "skjak.no", + "skj\u00e5k.no", + "xn--skjk-soa.no", + "skodje.no", + "skanland.no", + "sk\u00e5nland.no", + "xn--sknland-fxa.no", + "skanit.no", + "sk\u00e1nit.no", + "xn--sknit-yqa.no", + "smola.no", + "sm\u00f8la.no", + "xn--smla-hra.no", + "snillfjord.no", + "snasa.no", + "sn\u00e5sa.no", + "xn--snsa-roa.no", + "snoasa.no", + "snaase.no", + "sn\u00e5ase.no", + "xn--snase-nra.no", + "sogndal.no", + "sokndal.no", + "sola.no", + "solund.no", + "songdalen.no", + "sortland.no", + "spydeberg.no", + "stange.no", + "stavanger.no", + "steigen.no", + "steinkjer.no", + "stjordal.no", + "stj\u00f8rdal.no", + "xn--stjrdal-s1a.no", + "stokke.no", + "stor-elvdal.no", + "stord.no", + "stordal.no", + "storfjord.no", + "omasvuotna.no", + "strand.no", + "stranda.no", + "stryn.no", + "sula.no", + "suldal.no", + "sund.no", + "sunndal.no", + "surnadal.no", + "sveio.no", + "svelvik.no", + "sykkylven.no", + "sogne.no", + "s\u00f8gne.no", + "xn--sgne-gra.no", + "somna.no", + "s\u00f8mna.no", + "xn--smna-gra.no", + "sondre-land.no", + "s\u00f8ndre-land.no", + "xn--sndre-land-0cb.no", + "sor-aurdal.no", + "s\u00f8r-aurdal.no", + "xn--sr-aurdal-l8a.no", + "sor-fron.no", + "s\u00f8r-fron.no", + "xn--sr-fron-q1a.no", + "sor-odal.no", + "s\u00f8r-odal.no", + "xn--sr-odal-q1a.no", + "sor-varanger.no", + "s\u00f8r-varanger.no", + "xn--sr-varanger-ggb.no", + "matta-varjjat.no", + "m\u00e1tta-v\u00e1rjjat.no", + "xn--mtta-vrjjat-k7af.no", + "sorfold.no", + "s\u00f8rfold.no", + "xn--srfold-bya.no", + "sorreisa.no", + "s\u00f8rreisa.no", + "xn--srreisa-q1a.no", + "sorum.no", + "s\u00f8rum.no", + "xn--srum-gra.no", + "tana.no", + "deatnu.no", + "time.no", + "tingvoll.no", + "tinn.no", + "tjeldsund.no", + "dielddanuorri.no", + "tjome.no", + "tj\u00f8me.no", + "xn--tjme-hra.no", + "tokke.no", + "tolga.no", + "torsken.no", + "tranoy.no", + "tran\u00f8y.no", + "xn--trany-yua.no", + "tromso.no", + "troms\u00f8.no", + "xn--troms-zua.no", + "tromsa.no", + "romsa.no", + "trondheim.no", + "troandin.no", + "trysil.no", + "trana.no", + "tr\u00e6na.no", + "xn--trna-woa.no", + "trogstad.no", + "tr\u00f8gstad.no", + "xn--trgstad-r1a.no", + "tvedestrand.no", + "tydal.no", + "tynset.no", + "tysfjord.no", + "divtasvuodna.no", + "divttasvuotna.no", + "tysnes.no", + "tysvar.no", + "tysv\u00e6r.no", + "xn--tysvr-vra.no", + "tonsberg.no", + "t\u00f8nsberg.no", + "xn--tnsberg-q1a.no", + "ullensaker.no", + "ullensvang.no", + "ulvik.no", + "utsira.no", + "vadso.no", + "vads\u00f8.no", + "xn--vads-jra.no", + "cahcesuolo.no", + "\u010d\u00e1hcesuolo.no", + "xn--hcesuolo-7ya35b.no", + "vaksdal.no", + "valle.no", + "vang.no", + "vanylven.no", + "vardo.no", + "vard\u00f8.no", + "xn--vard-jra.no", + "varggat.no", + "v\u00e1rgg\u00e1t.no", + "xn--vrggt-xqad.no", + "vefsn.no", + "vaapste.no", + "vega.no", + "vegarshei.no", + "veg\u00e5rshei.no", + "xn--vegrshei-c0a.no", + "vennesla.no", + "verdal.no", + "verran.no", + "vestby.no", + "vestnes.no", + "vestre-slidre.no", + "vestre-toten.no", + "vestvagoy.no", + "vestv\u00e5g\u00f8y.no", + "xn--vestvgy-ixa6o.no", + "vevelstad.no", + "vik.no", + "vikna.no", + "vindafjord.no", + "volda.no", + "voss.no", + "varoy.no", + "v\u00e6r\u00f8y.no", + "xn--vry-yla5g.no", + "vagan.no", + "v\u00e5gan.no", + "xn--vgan-qoa.no", + "voagat.no", + "vagsoy.no", + "v\u00e5gs\u00f8y.no", + "xn--vgsy-qoa0j.no", + "vaga.no", + "v\u00e5g\u00e5.no", + "xn--vg-yiab.no", + "valer.ostfold.no", + "v\u00e5ler.\u00f8stfold.no", + "xn--vler-qoa.xn--stfold-9xa.no", + "valer.hedmark.no", + "v\u00e5ler.hedmark.no", + "xn--vler-qoa.hedmark.no", + "nr", + "biz.nr", + "info.nr", + "gov.nr", + "edu.nr", + "org.nr", + "net.nr", + "com.nr", + "nu", + "org", + "pa", + "ac.pa", + "gob.pa", + "com.pa", + "org.pa", + "sld.pa", + "edu.pa", + "net.pa", + "ing.pa", + "abo.pa", + "med.pa", + "nom.pa", + "pe", + "edu.pe", + "gob.pe", + "nom.pe", + "mil.pe", + "org.pe", + "com.pe", + "net.pe", + "pf", + "com.pf", + "org.pf", + "edu.pf", + "ph", + "com.ph", + "net.ph", + "org.ph", + "gov.ph", + "edu.ph", + "ngo.ph", + "mil.ph", + "i.ph", + "pk", + "com.pk", + "net.pk", + "edu.pk", + "org.pk", + "fam.pk", + "biz.pk", + "web.pk", + "gov.pk", + "gob.pk", + "gok.pk", + "gon.pk", + "gop.pk", + "gos.pk", + "info.pk", + "pl", + "aid.pl", + "agro.pl", + "atm.pl", + "auto.pl", + "biz.pl", + "com.pl", + "edu.pl", + "gmina.pl", + "gsm.pl", + "info.pl", + "mail.pl", + "miasta.pl", + "media.pl", + "mil.pl", + "net.pl", + "nieruchomosci.pl", + "nom.pl", + "org.pl", + "pc.pl", + "powiat.pl", + "priv.pl", + "realestate.pl", + "rel.pl", + "sex.pl", + "shop.pl", + "sklep.pl", + "sos.pl", + "szkola.pl", + "targi.pl", + "tm.pl", + "tourism.pl", + "travel.pl", + "turystyka.pl", + "6bone.pl", + "art.pl", + "mbone.pl", + "gov.pl", + "uw.gov.pl", + "um.gov.pl", + "ug.gov.pl", + "upow.gov.pl", + "starostwo.gov.pl", + "so.gov.pl", + "sr.gov.pl", + "po.gov.pl", + "pa.gov.pl", + "ngo.pl", + "irc.pl", + "usenet.pl", + "augustow.pl", + "babia-gora.pl", + "bedzin.pl", + "beskidy.pl", + "bialowieza.pl", + "bialystok.pl", + "bielawa.pl", + "bieszczady.pl", + "boleslawiec.pl", + "bydgoszcz.pl", + "bytom.pl", + "cieszyn.pl", + "czeladz.pl", + "czest.pl", + "dlugoleka.pl", + "elblag.pl", + "elk.pl", + "glogow.pl", + "gniezno.pl", + "gorlice.pl", + "grajewo.pl", + "ilawa.pl", + "jaworzno.pl", + "jelenia-gora.pl", + "jgora.pl", + "kalisz.pl", + "kazimierz-dolny.pl", + "karpacz.pl", + "kartuzy.pl", + "kaszuby.pl", + "katowice.pl", + "kepno.pl", + "ketrzyn.pl", + "klodzko.pl", + "kobierzyce.pl", + "kolobrzeg.pl", + "konin.pl", + "konskowola.pl", + "kutno.pl", + "lapy.pl", + "lebork.pl", + "legnica.pl", + "lezajsk.pl", + "limanowa.pl", + "lomza.pl", + "lowicz.pl", + "lubin.pl", + "lukow.pl", + "malbork.pl", + "malopolska.pl", + "mazowsze.pl", + "mazury.pl", + "mielec.pl", + "mielno.pl", + "mragowo.pl", + "naklo.pl", + "nowaruda.pl", + "nysa.pl", + "olawa.pl", + "olecko.pl", + "olkusz.pl", + "olsztyn.pl", + "opoczno.pl", + "opole.pl", + "ostroda.pl", + "ostroleka.pl", + "ostrowiec.pl", + "ostrowwlkp.pl", + "pila.pl", + "pisz.pl", + "podhale.pl", + "podlasie.pl", + "polkowice.pl", + "pomorze.pl", + "pomorskie.pl", + "prochowice.pl", + "pruszkow.pl", + "przeworsk.pl", + "pulawy.pl", + "radom.pl", + "rawa-maz.pl", + "rybnik.pl", + "rzeszow.pl", + "sanok.pl", + "sejny.pl", + "siedlce.pl", + "slask.pl", + "slupsk.pl", + "sosnowiec.pl", + "stalowa-wola.pl", + "skoczow.pl", + "starachowice.pl", + "stargard.pl", + "suwalki.pl", + "swidnica.pl", + "swiebodzin.pl", + "swinoujscie.pl", + "szczecin.pl", + "szczytno.pl", + "tarnobrzeg.pl", + "tgory.pl", + "turek.pl", + "tychy.pl", + "ustka.pl", + "walbrzych.pl", + "warmia.pl", + "warszawa.pl", + "waw.pl", + "wegrow.pl", + "wielun.pl", + "wlocl.pl", + "wloclawek.pl", + "wodzislaw.pl", + "wolomin.pl", + "wroclaw.pl", + "zachpomor.pl", + "zagan.pl", + "zarow.pl", + "zgora.pl", + "zgorzelec.pl", + "gda.pl", + "gdansk.pl", + "gdynia.pl", + "med.pl", + "sopot.pl", + "gliwice.pl", + "krakow.pl", + "poznan.pl", + "wroc.pl", + "zakopane.pl", + "pm", + "pn", + "gov.pn", + "co.pn", + "org.pn", + "edu.pn", + "net.pn", + "pr", + "com.pr", + "net.pr", + "org.pr", + "gov.pr", + "edu.pr", + "isla.pr", + "pro.pr", + "biz.pr", + "info.pr", + "name.pr", + "est.pr", + "prof.pr", + "ac.pr", + "pro", + "aca.pro", + "bar.pro", + "cpa.pro", + "jur.pro", + "law.pro", + "med.pro", + "eng.pro", + "ps", + "edu.ps", + "gov.ps", + "sec.ps", + "plo.ps", + "com.ps", + "org.ps", + "net.ps", + "pt", + "net.pt", + "gov.pt", + "org.pt", + "edu.pt", + "int.pt", + "publ.pt", + "com.pt", + "nome.pt", + "pw", + "co.pw", + "ne.pw", + "or.pw", + "ed.pw", + "go.pw", + "belau.pw", + "qa", + "com.qa", + "edu.qa", + "gov.qa", + "mil.qa", + "name.qa", + "net.qa", + "org.qa", + "sch.qa", + "re", + "com.re", + "asso.re", + "nom.re", + "ro", + "com.ro", + "org.ro", + "tm.ro", + "nt.ro", + "nom.ro", + "info.ro", + "rec.ro", + "arts.ro", + "firm.ro", + "store.ro", + "www.ro", + "rs", + "co.rs", + "org.rs", + "edu.rs", + "ac.rs", + "gov.rs", + "in.rs", + "ru", + "ac.ru", + "com.ru", + "edu.ru", + "int.ru", + "net.ru", + "org.ru", + "pp.ru", + "adygeya.ru", + "altai.ru", + "amur.ru", + "arkhangelsk.ru", + "astrakhan.ru", + "bashkiria.ru", + "belgorod.ru", + "bir.ru", + "bryansk.ru", + "buryatia.ru", + "cbg.ru", + "chel.ru", + "chelyabinsk.ru", + "chita.ru", + "chukotka.ru", + "chuvashia.ru", + "dagestan.ru", + "dudinka.ru", + "e-burg.ru", + "grozny.ru", + "irkutsk.ru", + "ivanovo.ru", + "izhevsk.ru", + "jar.ru", + "joshkar-ola.ru", + "kalmykia.ru", + "kaluga.ru", + "kamchatka.ru", + "karelia.ru", + "kazan.ru", + "kchr.ru", + "kemerovo.ru", + "khabarovsk.ru", + "khakassia.ru", + "khv.ru", + "kirov.ru", + "koenig.ru", + "komi.ru", + "kostroma.ru", + "krasnoyarsk.ru", + "kuban.ru", + "kurgan.ru", + "kursk.ru", + "lipetsk.ru", + "magadan.ru", + "mari.ru", + "mari-el.ru", + "marine.ru", + "mordovia.ru", + "mosreg.ru", + "msk.ru", + "murmansk.ru", + "nalchik.ru", + "nnov.ru", + "nov.ru", + "novosibirsk.ru", + "nsk.ru", + "omsk.ru", + "orenburg.ru", + "oryol.ru", + "palana.ru", + "penza.ru", + "perm.ru", + "pskov.ru", + "ptz.ru", + "rnd.ru", + "ryazan.ru", + "sakhalin.ru", + "samara.ru", + "saratov.ru", + "simbirsk.ru", + "smolensk.ru", + "spb.ru", + "stavropol.ru", + "stv.ru", + "surgut.ru", + "tambov.ru", + "tatarstan.ru", + "tom.ru", + "tomsk.ru", + "tsaritsyn.ru", + "tsk.ru", + "tula.ru", + "tuva.ru", + "tver.ru", + "tyumen.ru", + "udm.ru", + "udmurtia.ru", + "ulan-ude.ru", + "vladikavkaz.ru", + "vladimir.ru", + "vladivostok.ru", + "volgograd.ru", + "vologda.ru", + "voronezh.ru", + "vrn.ru", + "vyatka.ru", + "yakutia.ru", + "yamal.ru", + "yaroslavl.ru", + "yekaterinburg.ru", + "yuzhno-sakhalinsk.ru", + "amursk.ru", + "baikal.ru", + "cmw.ru", + "fareast.ru", + "jamal.ru", + "kms.ru", + "k-uralsk.ru", + "kustanai.ru", + "kuzbass.ru", + "magnitka.ru", + "mytis.ru", + "nakhodka.ru", + "nkz.ru", + "norilsk.ru", + "oskol.ru", + "pyatigorsk.ru", + "rubtsovsk.ru", + "snz.ru", + "syzran.ru", + "vdonsk.ru", + "zgrad.ru", + "gov.ru", + "mil.ru", + "test.ru", + "rw", + "gov.rw", + "net.rw", + "edu.rw", + "ac.rw", + "com.rw", + "co.rw", + "int.rw", + "mil.rw", + "gouv.rw", + "sa", + "com.sa", + "net.sa", + "org.sa", + "gov.sa", + "med.sa", + "pub.sa", + "edu.sa", + "sch.sa", + "sb", + "com.sb", + "edu.sb", + "gov.sb", + "net.sb", + "org.sb", + "sc", + "com.sc", + "gov.sc", + "net.sc", + "org.sc", + "edu.sc", + "sd", + "com.sd", + "net.sd", + "org.sd", + "edu.sd", + "med.sd", + "tv.sd", + "gov.sd", + "info.sd", + "se", + "a.se", + "ac.se", + "b.se", + "bd.se", + "brand.se", + "c.se", + "d.se", + "e.se", + "f.se", + "fh.se", + "fhsk.se", + "fhv.se", + "g.se", + "h.se", + "i.se", + "k.se", + "komforb.se", + "kommunalforbund.se", + "komvux.se", + "l.se", + "lanbib.se", + "m.se", + "n.se", + "naturbruksgymn.se", + "o.se", + "org.se", + "p.se", + "parti.se", + "pp.se", + "press.se", + "r.se", + "s.se", + "sshn.se", + "t.se", + "tm.se", + "u.se", + "w.se", + "x.se", + "y.se", + "z.se", + "sg", + "com.sg", + "net.sg", + "org.sg", + "gov.sg", + "edu.sg", + "per.sg", + "sh", + "si", + "sk", + "sl", + "com.sl", + "net.sl", + "edu.sl", + "gov.sl", + "org.sl", + "sm", + "sn", + "art.sn", + "com.sn", + "edu.sn", + "gouv.sn", + "org.sn", + "perso.sn", + "univ.sn", + "so", + "com.so", + "net.so", + "org.so", + "sr", + "st", + "co.st", + "com.st", + "consulado.st", + "edu.st", + "embaixada.st", + "gov.st", + "mil.st", + "net.st", + "org.st", + "principe.st", + "saotome.st", + "store.st", + "su", + "sx", + "gov.sx", + "sy", + "edu.sy", + "gov.sy", + "net.sy", + "mil.sy", + "com.sy", + "org.sy", + "sz", + "co.sz", + "ac.sz", + "org.sz", + "tc", + "td", + "tel", + "tf", + "tg", + "th", + "ac.th", + "co.th", + "go.th", + "in.th", + "mi.th", + "net.th", + "or.th", + "tj", + "ac.tj", + "biz.tj", + "co.tj", + "com.tj", + "edu.tj", + "go.tj", + "gov.tj", + "int.tj", + "mil.tj", + "name.tj", + "net.tj", + "nic.tj", + "org.tj", + "test.tj", + "web.tj", + "tk", + "tl", + "gov.tl", + "tm", + "com.tm", + "co.tm", + "org.tm", + "net.tm", + "nom.tm", + "gov.tm", + "mil.tm", + "edu.tm", + "tn", + "com.tn", + "ens.tn", + "fin.tn", + "gov.tn", + "ind.tn", + "intl.tn", + "nat.tn", + "net.tn", + "org.tn", + "info.tn", + "perso.tn", + "tourism.tn", + "edunet.tn", + "rnrt.tn", + "rns.tn", + "rnu.tn", + "mincom.tn", + "agrinet.tn", + "defense.tn", + "turen.tn", + "to", + "com.to", + "gov.to", + "net.to", + "org.to", + "edu.to", + "mil.to", + "gov.nc.tr", + "travel", + "tt", + "co.tt", + "com.tt", + "org.tt", + "net.tt", + "biz.tt", + "info.tt", + "pro.tt", + "int.tt", + "coop.tt", + "jobs.tt", + "mobi.tt", + "travel.tt", + "museum.tt", + "aero.tt", + "name.tt", + "gov.tt", + "edu.tt", + "tv", + "tw", + "edu.tw", + "gov.tw", + "mil.tw", + "com.tw", + "net.tw", + "org.tw", + "idv.tw", + "game.tw", + "ebiz.tw", + "club.tw", + "\u7db2\u8def.tw", + "xn--zf0ao64a.tw", + "\u7d44\u7e54.tw", + "xn--uc0atv.tw", + "\u5546\u696d.tw", + "xn--czrw28b.tw", + "ac.tz", + "co.tz", + "go.tz", + "mil.tz", + "ne.tz", + "or.tz", + "sc.tz", + "ua", + "com.ua", + "edu.ua", + "gov.ua", + "in.ua", + "net.ua", + "org.ua", + "cherkassy.ua", + "chernigov.ua", + "chernovtsy.ua", + "ck.ua", + "cn.ua", + "crimea.ua", + "cv.ua", + "dn.ua", + "dnepropetrovsk.ua", + "donetsk.ua", + "dp.ua", + "if.ua", + "ivano-frankivsk.ua", + "kh.ua", + "kharkov.ua", + "kherson.ua", + "khmelnitskiy.ua", + "kiev.ua", + "kirovograd.ua", + "km.ua", + "kr.ua", + "ks.ua", + "kv.ua", + "lg.ua", + "lugansk.ua", + "lutsk.ua", + "lviv.ua", + "mk.ua", + "nikolaev.ua", + "od.ua", + "odessa.ua", + "pl.ua", + "poltava.ua", + "rovno.ua", + "rv.ua", + "sebastopol.ua", + "sumy.ua", + "te.ua", + "ternopil.ua", + "uzhgorod.ua", + "vinnica.ua", + "vn.ua", + "zaporizhzhe.ua", + "zp.ua", + "zhitomir.ua", + "zt.ua", + "co.ua", + "pp.ua", + "ug", + "co.ug", + "or.ug", + "ac.ug", + "sc.ug", + "go.ug", + "ne.ug", + "com.ug", + "org.ug", + "us", + "dni.us", + "fed.us", + "isa.us", + "kids.us", + "nsn.us", + "ak.us", + "al.us", + "ar.us", + "as.us", + "az.us", + "ca.us", + "co.us", + "ct.us", + "dc.us", + "de.us", + "fl.us", + "ga.us", + "gu.us", + "hi.us", + "ia.us", + "id.us", + "il.us", + "in.us", + "ks.us", + "ky.us", + "la.us", + "ma.us", + "md.us", + "me.us", + "mi.us", + "mn.us", + "mo.us", + "ms.us", + "mt.us", + "nc.us", + "nd.us", + "ne.us", + "nh.us", + "nj.us", + "nm.us", + "nv.us", + "ny.us", + "oh.us", + "ok.us", + "or.us", + "pa.us", + "pr.us", + "ri.us", + "sc.us", + "sd.us", + "tn.us", + "tx.us", + "ut.us", + "vi.us", + "vt.us", + "va.us", + "wa.us", + "wi.us", + "wv.us", + "wy.us", + "k12.ak.us", + "k12.al.us", + "k12.ar.us", + "k12.as.us", + "k12.az.us", + "k12.ca.us", + "k12.co.us", + "k12.ct.us", + "k12.dc.us", + "k12.de.us", + "k12.fl.us", + "k12.ga.us", + "k12.gu.us", + "k12.ia.us", + "k12.id.us", + "k12.il.us", + "k12.in.us", + "k12.ks.us", + "k12.ky.us", + "k12.la.us", + "k12.ma.us", + "k12.md.us", + "k12.me.us", + "k12.mi.us", + "k12.mn.us", + "k12.mo.us", + "k12.ms.us", + "k12.mt.us", + "k12.nc.us", + "k12.nd.us", + "k12.ne.us", + "k12.nh.us", + "k12.nj.us", + "k12.nm.us", + "k12.nv.us", + "k12.ny.us", + "k12.oh.us", + "k12.ok.us", + "k12.or.us", + "k12.pa.us", + "k12.pr.us", + "k12.ri.us", + "k12.sc.us", + "k12.sd.us", + "k12.tn.us", + "k12.tx.us", + "k12.ut.us", + "k12.vi.us", + "k12.vt.us", + "k12.va.us", + "k12.wa.us", + "k12.wi.us", + "k12.wv.us", + "k12.wy.us", + "cc.ak.us", + "cc.al.us", + "cc.ar.us", + "cc.as.us", + "cc.az.us", + "cc.ca.us", + "cc.co.us", + "cc.ct.us", + "cc.dc.us", + "cc.de.us", + "cc.fl.us", + "cc.ga.us", + "cc.gu.us", + "cc.hi.us", + "cc.ia.us", + "cc.id.us", + "cc.il.us", + "cc.in.us", + "cc.ks.us", + "cc.ky.us", + "cc.la.us", + "cc.ma.us", + "cc.md.us", + "cc.me.us", + "cc.mi.us", + "cc.mn.us", + "cc.mo.us", + "cc.ms.us", + "cc.mt.us", + "cc.nc.us", + "cc.nd.us", + "cc.ne.us", + "cc.nh.us", + "cc.nj.us", + "cc.nm.us", + "cc.nv.us", + "cc.ny.us", + "cc.oh.us", + "cc.ok.us", + "cc.or.us", + "cc.pa.us", + "cc.pr.us", + "cc.ri.us", + "cc.sc.us", + "cc.sd.us", + "cc.tn.us", + "cc.tx.us", + "cc.ut.us", + "cc.vi.us", + "cc.vt.us", + "cc.va.us", + "cc.wa.us", + "cc.wi.us", + "cc.wv.us", + "cc.wy.us", + "lib.ak.us", + "lib.al.us", + "lib.ar.us", + "lib.as.us", + "lib.az.us", + "lib.ca.us", + "lib.co.us", + "lib.ct.us", + "lib.dc.us", + "lib.de.us", + "lib.fl.us", + "lib.ga.us", + "lib.gu.us", + "lib.hi.us", + "lib.ia.us", + "lib.id.us", + "lib.il.us", + "lib.in.us", + "lib.ks.us", + "lib.ky.us", + "lib.la.us", + "lib.ma.us", + "lib.md.us", + "lib.me.us", + "lib.mi.us", + "lib.mn.us", + "lib.mo.us", + "lib.ms.us", + "lib.mt.us", + "lib.nc.us", + "lib.nd.us", + "lib.ne.us", + "lib.nh.us", + "lib.nj.us", + "lib.nm.us", + "lib.nv.us", + "lib.ny.us", + "lib.oh.us", + "lib.ok.us", + "lib.or.us", + "lib.pa.us", + "lib.pr.us", + "lib.ri.us", + "lib.sc.us", + "lib.sd.us", + "lib.tn.us", + "lib.tx.us", + "lib.ut.us", + "lib.vi.us", + "lib.vt.us", + "lib.va.us", + "lib.wa.us", + "lib.wi.us", + "lib.wv.us", + "lib.wy.us", + "pvt.k12.ma.us", + "chtr.k12.ma.us", + "paroch.k12.ma.us", + "uz", + "co.uz", + "com.uz", + "net.uz", + "org.uz", + "va", + "vc", + "com.vc", + "net.vc", + "org.vc", + "gov.vc", + "mil.vc", + "edu.vc", + "vg", + "vi", + "co.vi", + "com.vi", + "k12.vi", + "net.vi", + "org.vi", + "vn", + "com.vn", + "net.vn", + "org.vn", + "edu.vn", + "gov.vn", + "int.vn", + "ac.vn", + "biz.vn", + "info.vn", + "name.vn", + "pro.vn", + "health.vn", + "vu", + "wf", + "ws", + "com.ws", + "net.ws", + "org.ws", + "gov.ws", + "edu.ws", + "yt", + "\u0627\u0645\u0627\u0631\u0627\u062a", + "xn--mgbaam7a8h", + "\u09ac\u09be\u0982\u09b2\u09be", + "xn--54b7fta0cc", + "\u4e2d\u56fd", + "xn--fiqs8s", + "\u4e2d\u570b", + "xn--fiqz9s", + "\u0627\u0644\u062c\u0632\u0627\u0626\u0631", + "xn--lgbbat1ad8j", + "\u0645\u0635\u0631", + "xn--wgbh1c", + "\u10d2\u10d4", + "xn--node", + "\u9999\u6e2f", + "xn--j6w193g", + "\u092d\u093e\u0930\u0924", + "xn--h2brj9c", + "\u0628\u06be\u0627\u0631\u062a", + "xn--mgbbh1a71e", + "\u0c2d\u0c3e\u0c30\u0c24\u0c4d", + "xn--fpcrj9c3d", + "\u0aad\u0abe\u0ab0\u0aa4", + "xn--gecrj9c", + "\u0a2d\u0a3e\u0a30\u0a24", + "xn--s9brj9c", + "\u09ad\u09be\u09b0\u09a4", + "xn--45brj9c", + "\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe", + "xn--xkc2dl3a5ee0h", + "\u0627\u06cc\u0631\u0627\u0646", + "xn--mgba3a4f16a", + "\u0627\u064a\u0631\u0627\u0646", + "xn--mgba3a4fra", + "\u0627\u0644\u0627\u0631\u062f\u0646", + "xn--mgbayh7gpa", + "\ud55c\uad6d", + "xn--3e0b707e", + "\u0dbd\u0d82\u0d9a\u0dcf", + "xn--fzc2c9e2c", + "\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8", + "xn--xkc2al3hye2a", + "\u0627\u0644\u0645\u063a\u0631\u0628", + "xn--mgbc0a9azcg", + "\u0639\u0645\u0627\u0646", + "xn--mgb9awbf", + "\u0641\u0644\u0633\u0637\u064a\u0646", + "xn--ygbi2ammx", + "\u0441\u0440\u0431", + "xn--90a3ac", + "\u0440\u0444", + "xn--p1ai", + "\u0642\u0637\u0631", + "xn--wgbl6a", + "\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629", + "xn--mgberp4a5d4ar", + "\u0627\u0644\u0633\u0639\u0648\u062f\u06cc\u0629", + "xn--mgberp4a5d4a87g", + "\u0627\u0644\u0633\u0639\u0648\u062f\u06cc\u06c3", + "xn--mgbqly7c0a67fbc", + "\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0647", + "xn--mgbqly7cvafr", + "\u0633\u0648\u0631\u064a\u0629", + "xn--ogbpf8fl", + "\u0633\u0648\u0631\u064a\u0627", + "xn--mgbtf8fl", + "\u65b0\u52a0\u5761", + "xn--yfro4i67o", + "\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd", + "xn--clchc0ea0b2g2a9gcd", + "\u0e44\u0e17\u0e22", + "xn--o3cw4h", + "\u062a\u0648\u0646\u0633", + "xn--pgbs0dh", + "\u53f0\u7063", + "xn--kpry57d", + "\u53f0\u6e7e", + "xn--kprw13d", + "\u81fa\u7063", + "xn--nnx388a", + "\u0443\u043a\u0440", + "xn--j1amh", + "\u0627\u0644\u064a\u0645\u0646", + "xn--mgb2ddes", + "xxx", + "biz.at", + "info.at", + "priv.at", + "co.ca", + "ar.com", + "br.com", + "cn.com", + "de.com", + "eu.com", + "gb.com", + "gr.com", + "hu.com", + "jpn.com", + "kr.com", + "no.com", + "qc.com", + "ru.com", + "sa.com", + "se.com", + "uk.com", + "us.com", + "uy.com", + "za.com", + "gb.net", + "jp.net", + "se.net", + "uk.net", + "ae.org", + "us.org", + "com.de", + "operaunite.com", + "appspot.com", + "iki.fi", + "c.la", + "za.net", + "za.org", + "co.nl", + "co.no", + "co.pl", + "dyndns-at-home.com", + "dyndns-at-work.com", + "dyndns-blog.com", + "dyndns-free.com", + "dyndns-home.com", + "dyndns-ip.com", + "dyndns-mail.com", + "dyndns-office.com", + "dyndns-pics.com", + "dyndns-remote.com", + "dyndns-server.com", + "dyndns-web.com", + "dyndns-wiki.com", + "dyndns-work.com", + "dyndns.biz", + "dyndns.info", + "dyndns.org", + "dyndns.tv", + "at-band-camp.net", + "ath.cx", + "barrel-of-knowledge.info", + "barrell-of-knowledge.info", + "better-than.tv", + "blogdns.com", + "blogdns.net", + "blogdns.org", + "blogsite.org", + "boldlygoingnowhere.org", + "broke-it.net", + "buyshouses.net", + "cechire.com", + "dnsalias.com", + "dnsalias.net", + "dnsalias.org", + "dnsdojo.com", + "dnsdojo.net", + "dnsdojo.org", + "does-it.net", + "doesntexist.com", + "doesntexist.org", + "dontexist.com", + "dontexist.net", + "dontexist.org", + "doomdns.com", + "doomdns.org", + "dvrdns.org", + "dyn-o-saur.com", + "dynalias.com", + "dynalias.net", + "dynalias.org", + "dynathome.net", + "dyndns.ws", + "endofinternet.net", + "endofinternet.org", + "endoftheinternet.org", + "est-a-la-maison.com", + "est-a-la-masion.com", + "est-le-patron.com", + "est-mon-blogueur.com", + "for-better.biz", + "for-more.biz", + "for-our.info", + "for-some.biz", + "for-the.biz", + "forgot.her.name", + "forgot.his.name", + "from-ak.com", + "from-al.com", + "from-ar.com", + "from-az.net", + "from-ca.com", + "from-co.net", + "from-ct.com", + "from-dc.com", + "from-de.com", + "from-fl.com", + "from-ga.com", + "from-hi.com", + "from-ia.com", + "from-id.com", + "from-il.com", + "from-in.com", + "from-ks.com", + "from-ky.com", + "from-la.net", + "from-ma.com", + "from-md.com", + "from-me.org", + "from-mi.com", + "from-mn.com", + "from-mo.com", + "from-ms.com", + "from-mt.com", + "from-nc.com", + "from-nd.com", + "from-ne.com", + "from-nh.com", + "from-nj.com", + "from-nm.com", + "from-nv.com", + "from-ny.net", + "from-oh.com", + "from-ok.com", + "from-or.com", + "from-pa.com", + "from-pr.com", + "from-ri.com", + "from-sc.com", + "from-sd.com", + "from-tn.com", + "from-tx.com", + "from-ut.com", + "from-va.com", + "from-vt.com", + "from-wa.com", + "from-wi.com", + "from-wv.com", + "from-wy.com", + "ftpaccess.cc", + "fuettertdasnetz.de", + "game-host.org", + "game-server.cc", + "getmyip.com", + "gets-it.net", + "go.dyndns.org", + "gotdns.com", + "gotdns.org", + "groks-the.info", + "groks-this.info", + "ham-radio-op.net", + "here-for-more.info", + "hobby-site.com", + "hobby-site.org", + "home.dyndns.org", + "homedns.org", + "homeftp.net", + "homeftp.org", + "homeip.net", + "homelinux.com", + "homelinux.net", + "homelinux.org", + "homeunix.com", + "homeunix.net", + "homeunix.org", + "iamallama.com", + "in-the-band.net", + "is-a-anarchist.com", + "is-a-blogger.com", + "is-a-bookkeeper.com", + "is-a-bruinsfan.org", + "is-a-bulls-fan.com", + "is-a-candidate.org", + "is-a-caterer.com", + "is-a-celticsfan.org", + "is-a-chef.com", + "is-a-chef.net", + "is-a-chef.org", + "is-a-conservative.com", + "is-a-cpa.com", + "is-a-cubicle-slave.com", + "is-a-democrat.com", + "is-a-designer.com", + "is-a-doctor.com", + "is-a-financialadvisor.com", + "is-a-geek.com", + "is-a-geek.net", + "is-a-geek.org", + "is-a-green.com", + "is-a-guru.com", + "is-a-hard-worker.com", + "is-a-hunter.com", + "is-a-knight.org", + "is-a-landscaper.com", + "is-a-lawyer.com", + "is-a-liberal.com", + "is-a-libertarian.com", + "is-a-linux-user.org", + "is-a-llama.com", + "is-a-musician.com", + "is-a-nascarfan.com", + "is-a-nurse.com", + "is-a-painter.com", + "is-a-patsfan.org", + "is-a-personaltrainer.com", + "is-a-photographer.com", + "is-a-player.com", + "is-a-republican.com", + "is-a-rockstar.com", + "is-a-socialist.com", + "is-a-soxfan.org", + "is-a-student.com", + "is-a-teacher.com", + "is-a-techie.com", + "is-a-therapist.com", + "is-an-accountant.com", + "is-an-actor.com", + "is-an-actress.com", + "is-an-anarchist.com", + "is-an-artist.com", + "is-an-engineer.com", + "is-an-entertainer.com", + "is-by.us", + "is-certified.com", + "is-found.org", + "is-gone.com", + "is-into-anime.com", + "is-into-cars.com", + "is-into-cartoons.com", + "is-into-games.com", + "is-leet.com", + "is-lost.org", + "is-not-certified.com", + "is-saved.org", + "is-slick.com", + "is-uberleet.com", + "is-very-bad.org", + "is-very-evil.org", + "is-very-good.org", + "is-very-nice.org", + "is-very-sweet.org", + "is-with-theband.com", + "isa-geek.com", + "isa-geek.net", + "isa-geek.org", + "isa-hockeynut.com", + "issmarterthanyou.com", + "isteingeek.de", + "istmein.de", + "kicks-ass.net", + "kicks-ass.org", + "knowsitall.info", + "land-4-sale.us", + "lebtimnetz.de", + "leitungsen.de", + "likes-pie.com", + "likescandy.com", + "merseine.nu", + "mine.nu", + "misconfused.org", + "mypets.ws", + "myphotos.cc", + "neat-url.com", + "office-on-the.net", + "on-the-web.tv", + "podzone.net", + "podzone.org", + "readmyblog.org", + "saves-the-whales.com", + "scrapper-site.net", + "scrapping.cc", + "selfip.biz", + "selfip.com", + "selfip.info", + "selfip.net", + "selfip.org", + "sells-for-less.com", + "sells-for-u.com", + "sells-it.net", + "sellsyourhome.org", + "servebbs.com", + "servebbs.net", + "servebbs.org", + "serveftp.net", + "serveftp.org", + "servegame.org", + "shacknet.nu", + "simple-url.com", + "space-to-rent.com", + "stuff-4-sale.org", + "stuff-4-sale.us", + "teaches-yoga.com", + "thruhere.net", + "traeumtgerade.de", + "webhop.biz", + "webhop.info", + "webhop.net", + "webhop.org", + "worse-than.tv", + "writesthisblog.com", + "tp", + "ng" + ); + + /** + * If a hostname is not in the EXCLUDE set, and if removing its + * leftmost component results in a name which is contained in this + * set, it is a TLD. + */ + static final ImmutableSet UNDER = ImmutableSet.of( + "ar", + "bd", + "bn", + "ck", + "cy", + "er", + "et", + "fj", + "fk", + "gt", + "gu", + "il", + "jm", + "aichi.jp", + "akita.jp", + "aomori.jp", + "chiba.jp", + "ehime.jp", + "fukui.jp", + "fukuoka.jp", + "fukushima.jp", + "gifu.jp", + "gunma.jp", + "hiroshima.jp", + "hokkaido.jp", + "hyogo.jp", + "ibaraki.jp", + "ishikawa.jp", + "iwate.jp", + "kagawa.jp", + "kagoshima.jp", + "kanagawa.jp", + "kawasaki.jp", + "kitakyushu.jp", + "kobe.jp", + "kochi.jp", + "kumamoto.jp", + "kyoto.jp", + "mie.jp", + "miyagi.jp", + "miyazaki.jp", + "nagano.jp", + "nagasaki.jp", + "nagoya.jp", + "nara.jp", + "niigata.jp", + "oita.jp", + "okayama.jp", + "okinawa.jp", + "osaka.jp", + "saga.jp", + "saitama.jp", + "sapporo.jp", + "sendai.jp", + "shiga.jp", + "shimane.jp", + "shizuoka.jp", + "tochigi.jp", + "tokushima.jp", + "tokyo.jp", + "tottori.jp", + "toyama.jp", + "wakayama.jp", + "yamagata.jp", + "yamaguchi.jp", + "yamanashi.jp", + "yokohama.jp", + "ke", + "kh", + "kw", + "mm", + "mt", + "mz", + "ni", + "np", + "nz", + "om", + "pg", + "py", + "sv", + "tr", + "uk", + "sch.uk", + "uy", + "ve", + "ye", + "za", + "zm", + "zw" + ); + + /** + * The elements in this set would pass the UNDER test, but are + * known not to be TLDs and are thus excluded from consideration. + */ + static final ImmutableSet EXCLUDED = ImmutableSet.of( + "congresodelalengua3.ar", + "educ.ar", + "gobiernoelectronico.ar", + "mecon.ar", + "nacion.ar", + "nic.ar", + "promocion.ar", + "retina.ar", + "uba.ar", + "www.ck", + "www.gt", + "metro.tokyo.jp", + "pref.aichi.jp", + "pref.akita.jp", + "pref.aomori.jp", + "pref.chiba.jp", + "pref.ehime.jp", + "pref.fukui.jp", + "pref.fukuoka.jp", + "pref.fukushima.jp", + "pref.gifu.jp", + "pref.gunma.jp", + "pref.hiroshima.jp", + "pref.hokkaido.jp", + "pref.hyogo.jp", + "pref.ibaraki.jp", + "pref.ishikawa.jp", + "pref.iwate.jp", + "pref.kagawa.jp", + "pref.kagoshima.jp", + "pref.kanagawa.jp", + "pref.kochi.jp", + "pref.kumamoto.jp", + "pref.kyoto.jp", + "pref.mie.jp", + "pref.miyagi.jp", + "pref.miyazaki.jp", + "pref.nagano.jp", + "pref.nagasaki.jp", + "pref.nara.jp", + "pref.niigata.jp", + "pref.oita.jp", + "pref.okayama.jp", + "pref.okinawa.jp", + "pref.osaka.jp", + "pref.saga.jp", + "pref.saitama.jp", + "pref.shiga.jp", + "pref.shimane.jp", + "pref.shizuoka.jp", + "pref.tochigi.jp", + "pref.tokushima.jp", + "pref.tottori.jp", + "pref.toyama.jp", + "pref.wakayama.jp", + "pref.yamagata.jp", + "pref.yamaguchi.jp", + "pref.yamanashi.jp", + "city.chiba.jp", + "city.fukuoka.jp", + "city.hiroshima.jp", + "city.kawasaki.jp", + "city.kitakyushu.jp", + "city.kobe.jp", + "city.kyoto.jp", + "city.nagoya.jp", + "city.niigata.jp", + "city.okayama.jp", + "city.osaka.jp", + "city.saitama.jp", + "city.sapporo.jp", + "city.sendai.jp", + "city.shizuoka.jp", + "city.yokohama.jp", + "mediaphone.om", + "nawrastelecom.om", + "nawras.om", + "omanmobile.om", + "omanpost.om", + "omantel.om", + "rakpetroleum.om", + "siemens.om", + "songfest.om", + "statecouncil.om", + "nic.tr", + "bl.uk", + "british-library.uk", + "icnet.uk", + "jet.uk", + "mod.uk", + "nel.uk", + "nhs.uk", + "nic.uk", + "nls.uk", + "national-library-scotland.uk", + "parliament.uk", + "police.uk" + ); +} + diff --git a/guava/src/com/google/common/net/package-info.java b/guava/src/com/google/common/net/package-info.java new file mode 100644 index 0000000..090a231 --- /dev/null +++ b/guava/src/com/google/common/net/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains utility methods and classes for working with net + * addresses (numeric IP and domain names). + * + *

    This package is a part of the open-source + * Guava libraries. + * + * @author Craig Berry + */ +@ParametersAreNonnullByDefault +package com.google.common.net; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/guava/src/com/google/common/primitives/AndroidInteger.java b/guava/src/com/google/common/primitives/AndroidInteger.java new file mode 100644 index 0000000..7fa8abd --- /dev/null +++ b/guava/src/com/google/common/primitives/AndroidInteger.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.CheckForNull; + +/** + * Static utility methods derived from Android's {@code Integer.java}. + */ +final class AndroidInteger { + /** + * See {@link Ints#tryParse(String)} for the public interface. + */ + @CheckForNull + static Integer tryParse(String string) { + return tryParse(string, 10); + } + + /** + * See {@link Ints#tryParse(String, int)} for the public interface. + */ + @CheckForNull + static Integer tryParse(String string, int radix) { + checkNotNull(string); + checkArgument(radix >= Character.MIN_RADIX, + "Invalid radix %s, min radix is %s", radix, Character.MIN_RADIX); + checkArgument(radix <= Character.MAX_RADIX, + "Invalid radix %s, max radix is %s", radix, Character.MAX_RADIX); + int length = string.length(), i = 0; + if (length == 0) { + return null; + } + boolean negative = string.charAt(i) == '-'; + if (negative && ++i == length) { + return null; + } + return tryParse(string, i, radix, negative); + } + + @CheckForNull + private static Integer tryParse(String string, int offset, int radix, + boolean negative) { + int max = Integer.MIN_VALUE / radix; + int result = 0, length = string.length(); + while (offset < length) { + int digit = Character.digit(string.charAt(offset++), radix); + if (digit == -1) { + return null; + } + if (max > result) { + return null; + } + int next = result * radix - digit; + if (next > result) { + return null; + } + result = next; + } + if (!negative) { + result = -result; + if (result < 0) { + return null; + } + } + // For GWT where ints do not overflow + if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) { + return null; + } + return result; + } + + private AndroidInteger() {} +} diff --git a/guava/src/com/google/common/primitives/Booleans.java b/guava/src/com/google/common/primitives/Booleans.java new file mode 100644 index 0000000..3601a8a --- /dev/null +++ b/guava/src/com/google/common/primitives/Booleans.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code boolean} primitives, that are not + * already found in either {@link Boolean} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Booleans { + private Booleans() {} + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Boolean) value).hashCode()}. + * + * @param value a primitive {@code boolean} value + * @return a hash code for the value + */ + public static int hashCode(boolean value) { + return value ? 1231 : 1237; + } + + /** + * Compares the two specified {@code boolean} values in the standard way + * ({@code false} is considered less than {@code true}). The sign of the + * value returned is the same as that of {@code ((Boolean) a).compareTo(b)}. + * + * @param a the first {@code boolean} to compare + * @param b the second {@code boolean} to compare + * @return a positive number if only {@code a} is {@code true}, a negative + * number if only {@code b} is true, or zero if {@code a == b} + */ + public static int compare(boolean a, boolean b) { + return (a == b) ? 0 : (a ? 1 : -1); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + *

    Note: consider representing the array as a {@link + * BitSet} instead, replacing {@code Booleans.contains(array, true)} + * with {@code !bitSet.isEmpty()} and {@code Booleans.contains(array, false)} + * with {@code bitSet.nextClearBit(0) == sizeOfBitSet}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(boolean[] array, boolean target) { + for (boolean value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + *

    Note: consider representing the array as a {@link BitSet} + * instead, and using {@link BitSet#nextSetBit(int)} or {@link + * BitSet#nextClearBit(int)}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(boolean[] array, boolean target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + boolean[] array, boolean target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(boolean[] array, boolean[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(boolean[] array, boolean target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + boolean[] array, boolean target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new boolean[] {a, b}, new boolean[] {}, new + * boolean[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code boolean} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static boolean[] concat(boolean[]... arrays) { + int length = 0; + for (boolean[] array : arrays) { + length += array.length; + } + boolean[] result = new boolean[length]; + int pos = 0; + for (boolean[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static boolean[] ensureCapacity( + boolean[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static boolean[] copyOf(boolean[] original, int length) { + boolean[] copy = new boolean[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code boolean} values separated + * by {@code separator}. For example, {@code join("-", false, true, false)} + * returns the string {@code "false-true-false"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code boolean} values, possibly empty + */ + public static String join(String separator, boolean... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 7); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code boolean} arrays + * lexicographically. That is, it compares, using {@link + * #compare(boolean, boolean)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, + * {@code [] < [false] < [false, true] < [true]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(boolean[], boolean[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(boolean[] left, boolean[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Booleans.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Copies a collection of {@code Boolean} instances into a new array of + * primitive {@code boolean} values. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + *

    Note: consider representing the collection as a {@link + * BitSet} instead. + * + * @param collection a collection of {@code Boolean} objects + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + */ + public static boolean[] toArray(Collection collection) { + if (collection instanceof BooleanArrayAsList) { + return ((BooleanArrayAsList) collection).toBooleanArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + boolean[] array = new boolean[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Boolean) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Boolean} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(boolean... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(backingArray); + } + + @GwtCompatible + private static class BooleanArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final boolean[] array; + final int start; + final int end; + + BooleanArrayAsList(boolean[] array) { + this(array, 0, array.length); + } + + BooleanArrayAsList(boolean[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Boolean get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Boolean) + && Booleans.indexOf(array, (Boolean) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.indexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.lastIndexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Boolean set(int index, Boolean element) { + checkElementIndex(index, size()); + boolean oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof BooleanArrayAsList) { + BooleanArrayAsList that = (BooleanArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Booleans.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 7); + builder.append(array[start] ? "[true" : "[false"); + for (int i = start + 1; i < end; i++) { + builder.append(array[i] ? ", true" : ", false"); + } + return builder.append(']').toString(); + } + + boolean[] toBooleanArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + boolean[] result = new boolean[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/Bytes.java b/guava/src/com/google/common/primitives/Bytes.java new file mode 100644 index 0000000..d9c5e1f --- /dev/null +++ b/guava/src/com/google/common/primitives/Bytes.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code byte} primitives, that are not + * already found in either {@link Byte} or {@link Arrays}, and interpret + * bytes as neither signed nor unsigned. The methods which specifically + * treat bytes as signed or unsigned are found in {@link SignedBytes} and {@link + * UnsignedBytes}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +// TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT +// javadoc? +@GwtCompatible +public final class Bytes { + private Bytes() {} + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Byte) value).hashCode()}. + * + * @param value a primitive {@code byte} value + * @return a hash code for the value + */ + public static int hashCode(byte value) { + return value; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(byte[] array, byte target) { + for (byte value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(byte[] array, byte target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + byte[] array, byte target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(byte[] array, byte[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(byte[] array, byte target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + byte[] array, byte target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new byte[] {a, b}, new byte[] {}, new + * byte[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code byte} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static byte[] ensureCapacity( + byte[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static byte[] copyOf(byte[] original, int length) { + byte[] copy = new byte[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code byte} value in the manner of {@link Number#byteValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static byte[] toArray(Collection collection) { + if (collection instanceof ByteArrayAsList) { + return ((ByteArrayAsList) collection).toByteArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + byte[] array = new byte[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).byteValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Byte} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(byte... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ByteArrayAsList(backingArray); + } + + @GwtCompatible + private static class ByteArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final byte[] array; + final int start; + final int end; + + ByteArrayAsList(byte[] array) { + this(array, 0, array.length); + } + + ByteArrayAsList(byte[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Byte get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Byte) + && Bytes.indexOf(array, (Byte) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.indexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.lastIndexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Byte set(int index, Byte element) { + checkElementIndex(index, size()); + byte oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ByteArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ByteArrayAsList) { + ByteArrayAsList that = (ByteArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Bytes.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + byte[] toByteArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + byte[] result = new byte[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/Chars.java b/guava/src/com/google/common/primitives/Chars.java new file mode 100644 index 0000000..241814a --- /dev/null +++ b/guava/src/com/google/common/primitives/Chars.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code char} primitives, that are not + * already found in either {@link Character} or {@link Arrays}. + * + *

    All the operations in this class treat {@code char} values strictly + * numerically; they are neither Unicode-aware nor locale-dependent. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Chars { + private Chars() {} + + /** + * The number of bytes required to represent a primitive {@code char} + * value. + */ + public static final int BYTES = Character.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Character) value).hashCode()}. + * + * @param value a primitive {@code char} value + * @return a hash code for the value + */ + public static int hashCode(char value) { + return value; + } + + /** + * Returns the {@code char} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code char} type + * @return the {@code char} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link + * Character#MAX_VALUE} or less than {@link Character#MIN_VALUE} + */ + public static char checkedCast(long value) { + char result = (char) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code char} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code char} if it is in the range of the + * {@code char} type, {@link Character#MAX_VALUE} if it is too large, + * or {@link Character#MIN_VALUE} if it is too small + */ + public static char saturatedCast(long value) { + if (value > Character.MAX_VALUE) { + return Character.MAX_VALUE; + } + if (value < Character.MIN_VALUE) { + return Character.MIN_VALUE; + } + return (char) value; + } + + /** + * Compares the two specified {@code char} values. The sign of the value + * returned is the same as that of {@code ((Character) a).compareTo(b)}. + * + * @param a the first {@code char} to compare + * @param b the second {@code char} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(char a, char b) { + return a - b; // safe due to restricted range + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(char[] array, char target) { + for (char value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(char[] array, char target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + char[] array, char target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(char[] array, char[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(char[] array, char target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + char[] array, char target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char min(char... array) { + checkArgument(array.length > 0); + char min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char max(char... array) { + checkArgument(array.length > 0); + char max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new char[] {a, b}, new char[] {}, new + * char[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code char} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static char[] concat(char[]... arrays) { + int length = 0; + for (char[] array : arrays) { + length += array.length; + } + char[] result = new char[length]; + int pos = 0; + for (char[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte + * array; equivalent to {@code + * ByteBuffer.allocate(2).putChar(value).array()}. For example, the input + * value {@code '\\u5432'} would yield the byte array {@code {0x54, 0x32}}. + * + *

    If you need to convert and concatenate several values (possibly even of + * different types), use a shared {@link java.nio.ByteBuffer} instance, or use + * {@link com.google.common.io.ByteStreams#newDataOutput()} to get a growable + * buffer. + */ + @GwtIncompatible("doesn't work") + public static byte[] toByteArray(char value) { + return new byte[] { + (byte) (value >> 8), + (byte) value}; + } + + /** + * Returns the {@code char} value whose big-endian representation is + * stored in the first 2 bytes of {@code bytes}; equivalent to {@code + * ByteBuffer.wrap(bytes).getChar()}. For example, the input byte array + * {@code {0x54, 0x32}} would yield the {@code char} value {@code '\\u5432'}. + * + *

    Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that + * library exposes much more flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 + * elements + */ + @GwtIncompatible("doesn't work") + public static char fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, + "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code char} value whose byte representation is the given 2 + * bytes, in big-endian order; equivalent to {@code Chars.fromByteArray(new + * byte[] {b1, b2})}. + * + * @since 7.0 + */ + @GwtIncompatible("doesn't work") + public static char fromBytes(byte b1, byte b2) { + return (char) ((b1 << 8) | (b2 & 0xFF)); + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static char[] ensureCapacity( + char[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static char[] copyOf(char[] original, int length) { + char[] copy = new char[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code char} values separated + * by {@code separator}. For example, {@code join("-", '1', '2', '3')} returns + * the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code char} values, possibly empty + */ + public static String join(String separator, char... array) { + checkNotNull(separator); + int len = array.length; + if (len == 0) { + return ""; + } + + StringBuilder builder + = new StringBuilder(len + separator.length() * (len - 1)); + builder.append(array[0]); + for (int i = 1; i < len; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code char} arrays + * lexicographically. That is, it compares, using {@link + * #compare(char, char)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, + * {@code [] < ['a'] < ['a', 'b'] < ['b']}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(char[], char[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(char[] left, char[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Chars.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Copies a collection of {@code Character} instances into a new array of + * primitive {@code char} values. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Character} objects + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + */ + public static char[] toArray(Collection collection) { + if (collection instanceof CharArrayAsList) { + return ((CharArrayAsList) collection).toCharArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + char[] array = new char[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Character) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Character} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(char... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new CharArrayAsList(backingArray); + } + + @GwtCompatible + private static class CharArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final char[] array; + final int start; + final int end; + + CharArrayAsList(char[] array) { + this(array, 0, array.length); + } + + CharArrayAsList(char[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Character get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Character) + && Chars.indexOf(array, (Character) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.indexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.lastIndexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Character set(int index, Character element) { + checkElementIndex(index, size()); + char oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new CharArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CharArrayAsList) { + CharArrayAsList that = (CharArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Chars.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 3); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + char[] toCharArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + char[] result = new char[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/Doubles.java b/guava/src/com/google/common/primitives/Doubles.java new file mode 100644 index 0000000..f108357 --- /dev/null +++ b/guava/src/com/google/common/primitives/Doubles.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Double.NEGATIVE_INFINITY; +import static java.lang.Double.POSITIVE_INFINITY; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code double} primitives, that are not + * already found in either {@link Double} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Doubles { + private Doubles() {} + + /** + * The number of bytes required to represent a primitive {@code double} + * value. + * + * @since 10.0 + */ + public static final int BYTES = Double.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Double) value).hashCode()}. + * + * @param value a primitive {@code double} value + * @return a hash code for the value + */ + public static int hashCode(double value) { + return ((Double) value).hashCode(); + // TODO(kevinb): do it this way when we can (GWT problem): + // long bits = Double.doubleToLongBits(value); + // return (int)(bits ^ (bits >>> 32)); + } + + /** + * Compares the two specified {@code double} values. The sign of the value + * returned is the same as that of ((Double) a).{@linkplain + * Double#compareTo compareTo}(b). As with that method, {@code NaN} is + * treated as greater than all other values, and {@code 0.0 > -0.0}. + * + * @param a the first {@code double} to compare + * @param b the second {@code double} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(double a, double b) { + return Double.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is + * equivalent to, but not necessarily implemented as, + * {@code !(Double.isInfinite(value) || Double.isNaN(value))}. + * + * @since 10.0 + */ + public static boolean isFinite(double value) { + return NEGATIVE_INFINITY < value & value < POSITIVE_INFINITY; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. Note that this always returns {@code false} when {@code + * target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(double[] array, double target) { + for (double value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. Note that this always returns {@code -1} when {@code target} + * is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(double[] array, double target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + double[] array, double target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + *

    Note that this always returns {@code -1} when {@code target} contains + * {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(double[] array, double[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. Note that this always returns {@code -1} when {@code target} + * is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(double[] array, double target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + double[] array, double target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of + * comparison as {@link Math#min(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double min(double... array) { + checkArgument(array.length > 0); + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules + * of comparison as {@link Math#max(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double max(double... array) { + checkArgument(array.length > 0); + double max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new double[] {a, b}, new double[] {}, new + * double[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code double} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static double[] concat(double[]... arrays) { + int length = 0; + for (double[] array : arrays) { + length += array.length; + } + double[] result = new double[length]; + int pos = 0; + for (double[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static double[] ensureCapacity( + double[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static double[] copyOf(double[] original, int length) { + double[] copy = new double[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code double} values, converted + * to strings as specified by {@link Double#toString(double)}, and separated + * by {@code separator}. For example, {@code join("-", 1.0, 2.0, 3.0)} returns + * the string {@code "1.0-2.0-3.0"}. + * + *

    Note that {@link Double#toString(double)} formats {@code double} + * differently in GWT sometimes. In the previous example, it returns the + * string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code double} values, possibly empty + */ + public static String join(String separator, double... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code double} arrays + * lexicographically. That is, it compares, using {@link + * #compare(double, double)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, + * {@code [] < [1.0] < [1.0, 2.0] < [2.0]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(double[], double[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(double[] left, double[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Doubles.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code double} value in the manner of {@link Number#doubleValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static double[] toArray(Collection collection) { + if (collection instanceof DoubleArrayAsList) { + return ((DoubleArrayAsList) collection).toDoubleArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + double[] array = new double[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).doubleValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Double} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + *

    The returned list may have unexpected behavior if it contains {@code + * NaN}, or if {@code NaN} is used as a parameter to any of its methods. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(double... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(backingArray); + } + + @GwtCompatible + private static class DoubleArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final double[] array; + final int start; + final int end; + + DoubleArrayAsList(double[] array) { + this(array, 0, array.length); + } + + DoubleArrayAsList(double[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Double get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Double) + && Doubles.indexOf(array, (Double) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.indexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.lastIndexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Double set(int index, Double element) { + checkElementIndex(index, size()); + double oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof DoubleArrayAsList) { + DoubleArrayAsList that = (DoubleArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Doubles.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + double[] toDoubleArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + double[] result = new double[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/Floats.java b/guava/src/com/google/common/primitives/Floats.java new file mode 100644 index 0000000..6d6cec4 --- /dev/null +++ b/guava/src/com/google/common/primitives/Floats.java @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Float.NEGATIVE_INFINITY; +import static java.lang.Float.POSITIVE_INFINITY; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code float} primitives, that are not + * already found in either {@link Float} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Floats { + private Floats() {} + + /** + * The number of bytes required to represent a primitive {@code float} + * value. + * + * @since 10.0 + */ + public static final int BYTES = Float.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Float) value).hashCode()}. + * + * @param value a primitive {@code float} value + * @return a hash code for the value + */ + public static int hashCode(float value) { + // TODO(kevinb): is there a better way, that's still gwt-safe? + return ((Float) value).hashCode(); + } + + /** + * Compares the two specified {@code float} values using {@link + * Float#compare(float, float)}. You may prefer to invoke that method + * directly; this method exists only for consistency with the other utilities + * in this package. + * + * @param a the first {@code float} to compare + * @param b the second {@code float} to compare + * @return the result of invoking {@link Float#compare(float, float)} + */ + public static int compare(float a, float b) { + return Float.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is + * equivalent to, but not necessarily implemented as, + * {@code !(Float.isInfinite(value) || Float.isNaN(value))}. + * + * @since 10.0 + */ + public static boolean isFinite(float value) { + return NEGATIVE_INFINITY < value & value < POSITIVE_INFINITY; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. Note that this always returns {@code false} when {@code + * target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(float[] array, float target) { + for (float value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. Note that this always returns {@code -1} when {@code target} + * is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(float[] array, float target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + float[] array, float target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + *

    Note that this always returns {@code -1} when {@code target} contains + * {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(float[] array, float[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. Note that this always returns {@code -1} when {@code target} + * is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(float[] array, float target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + float[] array, float target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of + * comparison as {@link Math#min(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float min(float... array) { + checkArgument(array.length > 0); + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules + * of comparison as {@link Math#min(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float max(float... array) { + checkArgument(array.length > 0); + float max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new float[] {a, b}, new float[] {}, new + * float[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code float} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static float[] concat(float[]... arrays) { + int length = 0; + for (float[] array : arrays) { + length += array.length; + } + float[] result = new float[length]; + int pos = 0; + for (float[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static float[] ensureCapacity( + float[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static float[] copyOf(float[] original, int length) { + float[] copy = new float[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code float} values, converted + * to strings as specified by {@link Float#toString(float)}, and separated by + * {@code separator}. For example, {@code join("-", 1.0f, 2.0f, 3.0f)} + * returns the string {@code "1.0-2.0-3.0"}. + * + *

    Note that {@link Float#toString(float)} formats {@code float} + * differently in GWT. In the previous example, it returns the string {@code + * "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code float} values, possibly empty + */ + public static String join(String separator, float... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code float} arrays + * lexicographically. That is, it compares, using {@link + * #compare(float, float)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, {@code [] < [1.0f] < [1.0f, 2.0f] + * < [2.0f]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(float[], float[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(float[] left, float[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Floats.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code float} value in the manner of {@link Number#floatValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static float[] toArray(Collection collection) { + if (collection instanceof FloatArrayAsList) { + return ((FloatArrayAsList) collection).toFloatArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + float[] array = new float[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).floatValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Float} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + *

    The returned list may have unexpected behavior if it contains {@code + * NaN}, or if {@code NaN} is used as a parameter to any of its methods. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(float... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new FloatArrayAsList(backingArray); + } + + @GwtCompatible + private static class FloatArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final float[] array; + final int start; + final int end; + + FloatArrayAsList(float[] array) { + this(array, 0, array.length); + } + + FloatArrayAsList(float[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Float get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Float) + && Floats.indexOf(array, (Float) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.indexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.lastIndexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Float set(int index, Float element) { + checkElementIndex(index, size()); + float oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new FloatArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof FloatArrayAsList) { + FloatArrayAsList that = (FloatArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Floats.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + float[] toFloatArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + float[] result = new float[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/Ints.java b/guava/src/com/google/common/primitives/Ints.java new file mode 100644 index 0000000..966066d --- /dev/null +++ b/guava/src/com/google/common/primitives/Ints.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +import javax.annotation.CheckForNull; + +/** + * Static utility methods pertaining to {@code int} primitives, that are not + * already found in either {@link Integer} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Ints { + private Ints() {} + + /** + * The number of bytes required to represent a primitive {@code int} + * value. + */ + public static final int BYTES = Integer.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as an {@code int}. + * + * @since 10.0 + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Integer) value).hashCode()}. + * + * @param value a primitive {@code int} value + * @return a hash code for the value + */ + public static int hashCode(int value) { + return value; + } + + /** + * Returns the {@code int} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code int} type + * @return the {@code int} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link + * Integer#MAX_VALUE} or less than {@link Integer#MIN_VALUE} + */ + public static int checkedCast(long value) { + int result = (int) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the + * {@code int} type, {@link Integer#MAX_VALUE} if it is too large, + * or {@link Integer#MIN_VALUE} if it is too small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } + + /** + * Compares the two specified {@code int} values. The sign of the value + * returned is the same as that of {@code ((Integer) a).compareTo(b)}. + * + * @param a the first {@code int} to compare + * @param b the second {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(int a, int b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(int[] array, int target) { + for (int value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(int[] array, int target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + int[] array, int target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(int[] array, int[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(int[] array, int target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + int[] array, int target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new int[] {a, b}, new int[] {}, new + * int[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code int} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static int[] concat(int[]... arrays) { + int length = 0; + for (int[] array : arrays) { + length += array.length; + } + int[] result = new int[length]; + int pos = 0; + for (int[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 4-element byte + * array; equivalent to {@code ByteBuffer.allocate(4).putInt(value).array()}. + * For example, the input value {@code 0x12131415} would yield the byte array + * {@code {0x12, 0x13, 0x14, 0x15}}. + * + *

    If you need to convert and concatenate several values (possibly even of + * different types), use a shared {@link java.nio.ByteBuffer} instance, or use + * {@link com.google.common.io.ByteStreams#newDataOutput()} to get a growable + * buffer. + */ + @GwtIncompatible("doesn't work") + public static byte[] toByteArray(int value) { + return new byte[] { + (byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value}; + } + + /** + * Returns the {@code int} value whose big-endian representation is stored in + * the first 4 bytes of {@code bytes}; equivalent to {@code + * ByteBuffer.wrap(bytes).getInt()}. For example, the input byte array {@code + * {0x12, 0x13, 0x14, 0x15, 0x33}} would yield the {@code int} value {@code + * 0x12131415}. + * + *

    Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that + * library exposes much more flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 4 elements + */ + @GwtIncompatible("doesn't work") + public static int fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, + "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]); + } + + /** + * Returns the {@code int} value whose byte representation is the given 4 + * bytes, in big-endian order; equivalent to {@code Ints.fromByteArray(new + * byte[] {b1, b2, b3, b4})}. + * + * @since 7.0 + */ + @GwtIncompatible("doesn't work") + public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { + return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static int[] ensureCapacity( + int[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static int[] copyOf(int[] original, int length) { + int[] copy = new int[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code int} values separated + * by {@code separator}. For example, {@code join("-", 1, 2, 3)} returns + * the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code int} arrays + * lexicographically. That is, it compares, using {@link + * #compare(int, int)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, {@code [] < [1] < [1, 2] < [2]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(int[], int[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Ints.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code int} value in the manner of {@link Number#intValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static int[] toArray(Collection collection) { + if (collection instanceof IntArrayAsList) { + return ((IntArrayAsList) collection).toIntArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).intValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Integer} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(int... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new IntArrayAsList(backingArray); + } + + @GwtCompatible + private static class IntArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final int[] array; + final int start; + final int end; + + IntArrayAsList(int[] array) { + this(array, 0, array.length); + } + + IntArrayAsList(int[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Integer get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Integer) + && Ints.indexOf(array, (Integer) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.indexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.lastIndexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Integer set(int index, Integer element) { + checkElementIndex(index, size()); + int oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new IntArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof IntArrayAsList) { + IntArrayAsList that = (IntArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Ints.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + int[] toIntArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + int[] result = new int[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } + + /** + * Parses the specified string as a signed decimal integer value. The ASCII + * character {@code '-'} ('\u002D') is recognized as the + * minus sign. + * + *

    Unlike {@link Integer#parseInt(String)}, this method returns + * {@code null} instead of throwing an exception if parsing fails. + * + *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even + * under JDK 7, despite the change to {@link Integer#parseInt(String)} for + * that version. + * + * @param string the string representation of an integer value + * @return the integer value represented by {@code string}, or {@code null} if + * {@code string} has a length of zero or cannot be parsed as an integer + * value + * @since 11.0 + */ + @Beta + @CheckForNull + @GwtIncompatible("TODO") + public static Integer tryParse(String string) { + return AndroidInteger.tryParse(string, 10); + } +} diff --git a/guava/src/com/google/common/primitives/Longs.java b/guava/src/com/google/common/primitives/Longs.java new file mode 100644 index 0000000..fd04178 --- /dev/null +++ b/guava/src/com/google/common/primitives/Longs.java @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code long} primitives, that are not + * already found in either {@link Long} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Longs { + private Longs() {} + + /** + * The number of bytes required to represent a primitive {@code long} + * value. + */ + public static final int BYTES = Long.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as a {@code long}. + * + * @since 10.0 + */ + public static final long MAX_POWER_OF_TWO = 1L << (Long.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Long) value).hashCode()}. + * + *

    This method always return the value specified by {@link + * Long#hashCode()} in java, which might be different from + * {@code ((Long) value).hashCode()} in GWT because {@link Long#hashCode()} + * in GWT does not obey the JRE contract. + * + * @param value a primitive {@code long} value + * @return a hash code for the value + */ + public static int hashCode(long value) { + return (int) (value ^ (value >>> 32)); + } + + /** + * Compares the two specified {@code long} values. The sign of the value + * returned is the same as that of {@code ((Long) a).compareTo(b)}. + * + * @param a the first {@code long} to compare + * @param b the second {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(long a, long b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(long[] array, long target) { + for (long value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(long[] array, long target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + long[] array, long target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(long[] array, long[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(long[] array, long target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + long[] array, long target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new long[] {a, b}, new long[] {}, new + * long[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code long} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static long[] concat(long[]... arrays) { + int length = 0; + for (long[] array : arrays) { + length += array.length; + } + long[] result = new long[length]; + int pos = 0; + for (long[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in an 8-element byte + * array; equivalent to {@code ByteBuffer.allocate(8).putLong(value).array()}. + * For example, the input value {@code 0x1213141516171819L} would yield the + * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}. + * + *

    If you need to convert and concatenate several values (possibly even of + * different types), use a shared {@link java.nio.ByteBuffer} instance, or use + * {@link com.google.common.io.ByteStreams#newDataOutput()} to get a growable + * buffer. + */ + public static byte[] toByteArray(long value) { + // Note that this code needs to stay compatible with GWT, which has known + // bugs when narrowing byte casts of long values occur. + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (value & 0xffL); + value >>= 8; + } + return result; + } + + /** + * Returns the {@code long} value whose big-endian representation is + * stored in the first 8 bytes of {@code bytes}; equivalent to {@code + * ByteBuffer.wrap(bytes).getLong()}. For example, the input byte array + * {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}} would yield the + * {@code long} value {@code 0x1213141516171819L}. + * + *

    Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that + * library exposes much more flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 8 + * elements + */ + public static long fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, + "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], bytes[6], bytes[7]) ; + } + + /** + * Returns the {@code long} value whose byte representation is the given 8 + * bytes, in big-endian order; equivalent to {@code Longs.fromByteArray(new + * byte[] {b1, b2, b3, b4, b5, b6, b7, b8})}. + * + * @since 7.0 + */ + public static long fromBytes(byte b1, byte b2, byte b3, byte b4, + byte b5, byte b6, byte b7, byte b8) { + return (b1 & 0xFFL) << 56 + | (b2 & 0xFFL) << 48 + | (b3 & 0xFFL) << 40 + | (b4 & 0xFFL) << 32 + | (b5 & 0xFFL) << 24 + | (b6 & 0xFFL) << 16 + | (b7 & 0xFFL) << 8 + | (b8 & 0xFFL); + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static long[] ensureCapacity( + long[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static long[] copyOf(long[] original, int length) { + long[] copy = new long[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code long} values separated + * by {@code separator}. For example, {@code join("-", 1L, 2L, 3L)} returns + * the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 10); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code long} arrays + * lexicographically. That is, it compares, using {@link + * #compare(long, long)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, + * {@code [] < [1L] < [1L, 2L] < [2L]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(long[], long[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Longs.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code long} value in the manner of {@link Number#longValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static long[] toArray(Collection collection) { + if (collection instanceof LongArrayAsList) { + return ((LongArrayAsList) collection).toLongArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + long[] array = new long[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).longValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Long} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(long... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new LongArrayAsList(backingArray); + } + + @GwtCompatible + private static class LongArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final long[] array; + final int start; + final int end; + + LongArrayAsList(long[] array) { + this(array, 0, array.length); + } + + LongArrayAsList(long[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Long get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Long) + && Longs.indexOf(array, (Long) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.indexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.lastIndexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Long set(int index, Long element) { + checkElementIndex(index, size()); + long oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new LongArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof LongArrayAsList) { + LongArrayAsList that = (LongArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Longs.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 10); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + long[] toLongArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + long[] result = new long[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/ParseRequest.java b/guava/src/com/google/common/primitives/ParseRequest.java new file mode 100644 index 0000000..98f29b4 --- /dev/null +++ b/guava/src/com/google/common/primitives/ParseRequest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import com.google.common.annotations.GwtCompatible; + +/** + * A string to be parsed as a number and the radix to interpret it in. + */ +@GwtCompatible +final class ParseRequest { + final String rawValue; + final int radix; + + private ParseRequest(String rawValue, int radix) { + this.rawValue = rawValue; + this.radix = radix; + } + + static ParseRequest fromString(String stringValue) { + if (stringValue.length() == 0) { + throw new NumberFormatException("empty string"); + } + + // Handle radix specifier if present + String rawValue; + int radix; + char firstChar = stringValue.charAt(0); + if (stringValue.startsWith("0x") || stringValue.startsWith("0X")) { + rawValue = stringValue.substring(2); + radix = 16; + } else if (firstChar == '#') { + rawValue = stringValue.substring(1); + radix = 16; + } else if (firstChar == '0' && stringValue.length() > 1) { + rawValue = stringValue.substring(1); + radix = 8; + } else { + rawValue = stringValue; + radix = 10; + } + + return new ParseRequest(rawValue, radix); + } +} diff --git a/guava/src/com/google/common/primitives/Primitives.java b/guava/src/com/google/common/primitives/Primitives.java new file mode 100644 index 0000000..08c6f31 --- /dev/null +++ b/guava/src/com/google/common/primitives/Primitives.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Contains static utility methods pertaining to primitive types and their + * corresponding wrapper types. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Primitives { + private Primitives() {} + + /** A map from primitive types to their corresponding wrapper types. */ + private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + + /** A map from wrapper types to their corresponding primitive types. */ + private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; + + // Sad that we can't use a BiMap. :( + + static { + Map, Class> primToWrap = new HashMap, Class>(16); + Map, Class> wrapToPrim = new HashMap, Class>(16); + + add(primToWrap, wrapToPrim, boolean.class, Boolean.class); + add(primToWrap, wrapToPrim, byte.class, Byte.class); + add(primToWrap, wrapToPrim, char.class, Character.class); + add(primToWrap, wrapToPrim, double.class, Double.class); + add(primToWrap, wrapToPrim, float.class, Float.class); + add(primToWrap, wrapToPrim, int.class, Integer.class); + add(primToWrap, wrapToPrim, long.class, Long.class); + add(primToWrap, wrapToPrim, short.class, Short.class); + add(primToWrap, wrapToPrim, void.class, Void.class); + + PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); + } + + private static void add(Map, Class> forward, + Map, Class> backward, Class key, Class value) { + forward.put(key, value); + backward.put(value, key); + } + + /** + * Returns an immutable set of all nine primitive types (including {@code + * void}). Note that a simpler way to test whether a {@code Class} instance + * is a member of this set is to call {@link Class#isPrimitive}. + * + * @since 3.0 + */ + public static Set> allPrimitiveTypes() { + return PRIMITIVE_TO_WRAPPER_TYPE.keySet(); + } + + /** + * Returns an immutable set of all nine primitive-wrapper types (including + * {@link Void}). + * + * @since 3.0 + */ + public static Set> allWrapperTypes() { + return WRAPPER_TO_PRIMITIVE_TYPE.keySet(); + } + + /** + * Returns {@code true} if {@code type} is one of the nine + * primitive-wrapper types, such as {@link Integer}. + * + * @see Class#isPrimitive + */ + public static boolean isWrapperType(Class type) { + return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type)); + } + + /** + * Returns the corresponding wrapper type of {@code type} if it is a primitive + * type; otherwise returns {@code type} itself. Idempotent. + *

    +   *     wrap(int.class) == Integer.class
    +   *     wrap(Integer.class) == Integer.class
    +   *     wrap(String.class) == String.class
    +   * 
    + */ + public static Class wrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); + return (wrapped == null) ? type : wrapped; + } + + /** + * Returns the corresponding primitive type of {@code type} if it is a + * wrapper type; otherwise returns {@code type} itself. Idempotent. + *
    +   *     unwrap(Integer.class) == int.class
    +   *     unwrap(int.class) == int.class
    +   *     unwrap(String.class) == String.class
    +   * 
    + */ + public static Class unwrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get(type); + return (unwrapped == null) ? type : unwrapped; + } +} diff --git a/guava/src/com/google/common/primitives/Shorts.java b/guava/src/com/google/common/primitives/Shorts.java new file mode 100644 index 0000000..db3d206 --- /dev/null +++ b/guava/src/com/google/common/primitives/Shorts.java @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code short} primitives, that are not + * already found in either {@link Short} or {@link Arrays}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Shorts { + private Shorts() {} + + /** + * The number of bytes required to represent a primitive {@code short} + * value. + */ + public static final int BYTES = Short.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as a {@code short}. + * + * @since 10.0 + */ + public static final short MAX_POWER_OF_TWO = 1 << (Short.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking + * {@code ((Short) value).hashCode()}. + * + * @param value a primitive {@code short} value + * @return a hash code for the value + */ + public static int hashCode(short value) { + return value; + } + + /** + * Returns the {@code short} value that is equal to {@code value}, if + * possible. + * + * @param value any value in the range of the {@code short} type + * @return the {@code short} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link + * Short#MAX_VALUE} or less than {@link Short#MIN_VALUE} + */ + public static short checkedCast(long value) { + short result = (short) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code short} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code short} if it is in the range of the + * {@code short} type, {@link Short#MAX_VALUE} if it is too large, + * or {@link Short#MIN_VALUE} if it is too small + */ + public static short saturatedCast(long value) { + if (value > Short.MAX_VALUE) { + return Short.MAX_VALUE; + } + if (value < Short.MIN_VALUE) { + return Short.MIN_VALUE; + } + return (short) value; + } + + /** + * Compares the two specified {@code short} values. The sign of the value + * returned is the same as that of {@code ((Short) a).compareTo(b)}. + * + * @param a the first {@code short} to compare + * @param b the second {@code short} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(short a, short b) { + return a - b; // safe due to restricted range + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in + * {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return {@code true} if {@code array[i] == target} for some value of {@code + * i} + */ + public static boolean contains(short[] array, short target) { + for (short value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public static int indexOf(short[] array, short target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf( + short[] array, short target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + * + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(short[] array, short[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + public static int lastIndexOf(short[] array, short target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf( + short[] array, short target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short min(short... array) { + checkArgument(array.length > 0); + short min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short max(short... array) { + checkArgument(array.length > 0); + short max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the values from each provided array combined into a single array. + * For example, {@code concat(new short[] {a, b}, new short[] {}, new + * short[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code short} arrays + * @return a single array containing all the values from the source arrays, in + * order + */ + public static short[] concat(short[]... arrays) { + int length = 0; + for (short[] array : arrays) { + length += array.length; + } + short[] result = new short[length]; + int pos = 0; + for (short[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte + * array; equivalent to {@code + * ByteBuffer.allocate(2).putShort(value).array()}. For example, the input + * value {@code (short) 0x1234} would yield the byte array {@code {0x12, + * 0x34}}. + * + *

    If you need to convert and concatenate several values (possibly even of + * different types), use a shared {@link java.nio.ByteBuffer} instance, or use + * {@link com.google.common.io.ByteStreams#newDataOutput()} to get a growable + * buffer. + */ + @GwtIncompatible("doesn't work") + public static byte[] toByteArray(short value) { + return new byte[] { + (byte) (value >> 8), + (byte) value}; + } + + /** + * Returns the {@code short} value whose big-endian representation is + * stored in the first 2 bytes of {@code bytes}; equivalent to {@code + * ByteBuffer.wrap(bytes).getShort()}. For example, the input byte array + * {@code {0x54, 0x32}} would yield the {@code short} value {@code 0x5432}. + * + *

    Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that + * library exposes much more flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 + * elements + */ + @GwtIncompatible("doesn't work") + public static short fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, + "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code short} value whose byte representation is the given 2 + * bytes, in big-endian order; equivalent to {@code Shorts.fromByteArray(new + * byte[] {b1, b2})}. + * + * @since 7.0 + */ + @GwtIncompatible("doesn't work") + public static short fromBytes(byte b1, byte b2) { + return (short) ((b1 << 8) | (b2 & 0xFF)); + } + + /** + * Returns an array containing the same values as {@code array}, but + * guaranteed to be of a specified minimum length. If {@code array} already + * has a length of at least {@code minLength}, it is returned directly. + * Otherwise, a new array of size {@code minLength + padding} is returned, + * containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is + * necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is + * negative + * @return an array containing the values of {@code array}, with guaranteed + * minimum length {@code minLength} + */ + public static short[] ensureCapacity( + short[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) + ? copyOf(array, minLength + padding) + : array; + } + + // Arrays.copyOf() requires Java 6 + private static short[] copyOf(short[] original, int length) { + short[] copy = new short[length]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, length)); + return copy; + } + + /** + * Returns a string containing the supplied {@code short} values separated + * by {@code separator}. For example, {@code join("-", (short) 1, (short) 2, + * (short) 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code short} values, possibly empty + */ + public static String join(String separator, short... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 6); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code short} arrays + * lexicographically. That is, it compares, using {@link + * #compare(short, short)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, {@code [] < [(short) 1] < + * [(short) 1, (short) 2] < [(short) 2]}. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link Arrays#equals(short[], short[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(short[] left, short[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Shorts.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to + * a {@code short} value in the manner of {@link Number#shortValue}. + * + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static short[] toArray(Collection collection) { + if (collection instanceof ShortArrayAsList) { + return ((ShortArrayAsList) collection).toShortArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + short[] array = new short[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).shortValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, + * but any attempt to set a value to {@code null} will result in a {@link + * NullPointerException}. + * + *

    The returned list maintains the values, but not the identities, of + * {@code Short} objects written to or read from it. For example, whether + * {@code list.get(0) == list.get(0)} is true for the returned list is + * unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(short... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ShortArrayAsList(backingArray); + } + + @GwtCompatible + private static class ShortArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final short[] array; + final int start; + final int end; + + ShortArrayAsList(short[] array) { + this(array, 0, array.length); + } + + ShortArrayAsList(short[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override public int size() { + return end - start; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Short get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Short) + && Shorts.indexOf(array, (Short) target, start, end) != -1; + } + + @Override public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.indexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.lastIndexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override public Short set(int index, Short element) { + checkElementIndex(index, size()); + short oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ShortArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ShortArrayAsList) { + ShortArrayAsList that = (ShortArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Shorts.hashCode(array[i]); + } + return result; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(size() * 6); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + short[] toShortArray() { + // Arrays.copyOfRange() is not available under GWT + int size = size(); + short[] result = new short[size]; + System.arraycopy(array, start, result, 0, size); + return result; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/guava/src/com/google/common/primitives/SignedBytes.java b/guava/src/com/google/common/primitives/SignedBytes.java new file mode 100644 index 0000000..07e3bda --- /dev/null +++ b/guava/src/com/google/common/primitives/SignedBytes.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code byte} primitives that + * interpret values as signed. The corresponding methods that treat the values + * as unsigned are found in {@link UnsignedBytes}, and the methods for which + * signedness is not an issue are in {@link Bytes}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +// TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT +// javadoc? +@GwtCompatible +public final class SignedBytes { + private SignedBytes() {} + + /** + * The largest power of two that can be represented as a signed {@code byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = 1 << 6; + + /** + * Returns the {@code byte} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code byte} type + * @return the {@code byte} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link + * Byte#MAX_VALUE} or less than {@link Byte#MIN_VALUE} + */ + public static byte checkedCast(long value) { + byte result = (byte) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code byte} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code byte} if it is in the range of the + * {@code byte} type, {@link Byte#MAX_VALUE} if it is too large, + * or {@link Byte#MIN_VALUE} if it is too small + */ + public static byte saturatedCast(long value) { + if (value > Byte.MAX_VALUE) { + return Byte.MAX_VALUE; + } + if (value < Byte.MIN_VALUE) { + return Byte.MIN_VALUE; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values. The sign of the value + * returned is the same as that of {@code ((Byte) a).compareTo(b)}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(byte a, byte b) { + return a - b; // safe due to restricted range + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + byte min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + byte max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns a string containing the supplied {@code byte} values separated + * by {@code separator}. For example, {@code join(":", 0x01, 0x02, -0x01)} + * returns the string {@code "1:2:-1"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays + * lexicographically. That is, it compares, using {@link + * #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter + * array as the lesser. For example, {@code [] < [0x01] < [0x01, 0x80] < + * [0x01, 0x7F] < [0x02]}. Values are treated as signed. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link java.util.Arrays#equals(byte[], byte[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = SignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } +} diff --git a/guava/src/com/google/common/primitives/UnsignedBytes.java b/guava/src/com/google/common/primitives/UnsignedBytes.java new file mode 100644 index 0000000..f82895b --- /dev/null +++ b/guava/src/com/google/common/primitives/UnsignedBytes.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code byte} primitives that interpret + * values as unsigned (that is, any negative value {@code b} is treated + * as the positive value {@code 256 + b}). The corresponding methods that treat + * the values as signed are found in {@link SignedBytes}, and the methods for + * which signedness is not an issue are in {@link Bytes}. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + * @author Kevin Bourrillion + * @author Martin Buchholz + * @author Hiroshi Yamauchi + * @author Louis Wasserman + * @since 1.0 + */ +public final class UnsignedBytes { + private UnsignedBytes() {} + + /** + * The largest power of two that can be represented as an unsigned {@code + * byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = (byte) 0x80; + + /** + * The largest value that fits into an unsigned byte. + * + * @since 13.0 + */ + public static final byte MAX_VALUE = (byte) 0xFF; + + private static final int UNSIGNED_MASK = 0xFF; + + /** + * Returns the value of the given byte as an integer, when treated as + * unsigned. That is, returns {@code value + 256} if {@code value} is + * negative; {@code value} itself otherwise. + * + * @since 6.0 + */ + public static int toInt(byte value) { + return value & UNSIGNED_MASK; + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is equal to + * {@code value}, if possible. + * + * @param value a value between 0 and 255 inclusive + * @return the {@code byte} value that, when treated as unsigned, equals + * {@code value} + * @throws IllegalArgumentException if {@code value} is negative or greater + * than 255 + */ + public static byte checkedCast(long value) { + checkArgument(value >> Byte.SIZE == 0, "out of range: %s", value); + return (byte) value; + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is nearest + * in value to {@code value}. + * + * @param value any {@code long} value + * @return {@code (byte) 255} if {@code value >= 255}, {@code (byte) 0} if + * {@code value <= 0}, and {@code value} cast to {@code byte} otherwise + */ + public static byte saturatedCast(long value) { + if (value > toInt(MAX_VALUE)) { + return MAX_VALUE; // -1 + } + if (value < 0) { + return (byte) 0; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values, treating them as unsigned + * values between 0 and 255 inclusive. For example, {@code (byte) -127} is + * considered greater than {@code (byte) 127} because it is seen as having + * the value of positive {@code 129}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; or zero if they are equal + */ + public static int compare(byte a, byte b) { + return toInt(a) - toInt(b); + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to + * every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + int min = toInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toInt(array[i]); + if (next < min) { + min = next; + } + } + return (byte) min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal + * to every other value in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + int max = toInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toInt(array[i]); + if (next > max) { + max = next; + } + } + return (byte) max; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + * @since 13.0 + */ + @Beta + public static String toString(byte x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated + * as unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + * @since 13.0 + */ + @Beta + public static String toString(byte x, int radix) { + checkArgument(radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", radix); + // Benchmarks indicate this is probably not worth optimizing. + return Integer.toString(toInt(x), radix); + } + + /** + * Returns the unsigned {@code byte} value represented by the given decimal string. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @since 13.0 + */ + @Beta + public static byte parseUnsignedByte(String string) { + return parseUnsignedByte(string, 10); + } + + /** + * Returns the unsigned {@code byte} value represented by a string with the given radix. + * + * @param string the string containing the unsigned {@code byte} representation to be parsed. + * @param radix the radix to use while parsing {@code string} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} + * with the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + * @since 13.0 + */ + @Beta + public static byte parseUnsignedByte(String string, int radix) { + int parse = Integer.parseInt(checkNotNull(string), radix); + // We need to throw a NumberFormatException, so we have to duplicate checkedCast. =( + if (parse >> Byte.SIZE == 0) { + return (byte) parse; + } else { + throw new NumberFormatException("out of range: " + parse); + } + } + + /** + * Returns a string containing the supplied {@code byte} values separated by + * {@code separator}. For example, {@code join(":", (byte) 1, (byte) 2, + * (byte) 255)} returns the string {@code "1:2:255"}. + * + * @param separator the text that should appear between consecutive values in + * the resulting string (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * (3 + separator.length())); + builder.append(toInt(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays + * lexicographically. That is, it compares, using {@link + * #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter + * array as the lesser. For example, {@code [] < [0x01] < [0x01, 0x7F] < + * [0x01, 0x80] < [0x02]}. Values are treated as unsigned. + * + *

    The returned comparator is inconsistent with {@link + * Object#equals(Object)} (since arrays support only identity equality), but + * it is consistent with {@link java.util.Arrays#equals(byte[], byte[])}. + * + * @see + * Lexicographical order article at Wikipedia + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparatorHolder.BEST_COMPARATOR; + } + + @VisibleForTesting + static Comparator lexicographicalComparatorJavaImpl() { + return LexicographicalComparatorHolder.PureJavaComparator.INSTANCE; + } + + /** + * Provides a lexicographical comparator implementation; either a Java + * implementation or a faster implementation based on {@link Unsafe}. + * + *

    Uses reflection to gracefully fall back to the Java implementation if + * {@code Unsafe} isn't available. + */ + @VisibleForTesting + static class LexicographicalComparatorHolder { + static final String UNSAFE_COMPARATOR_NAME = + LexicographicalComparatorHolder.class.getName() + "$UnsafeComparator"; + + static final Comparator BEST_COMPARATOR = getBestComparator(); + + @VisibleForTesting + enum UnsafeComparator implements Comparator { + INSTANCE; + + static final boolean littleEndian = + ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN); + + /* + * The following static final fields exist for performance reasons. + * + * In UnsignedBytesBenchmark, accessing the following objects via static + * final fields is the fastest (more than twice as fast as the Java + * implementation, vs ~1.5x with non-final static fields, on x86_32) + * under the Hotspot server compiler. The reason is obviously that the + * non-final fields need to be reloaded inside the loop. + * + * And, no, defining (final or not) local variables out of the loop still + * isn't as good because the null check on the theUnsafe object remains + * inside the loop and BYTE_ARRAY_BASE_OFFSET doesn't get + * constant-folded. + * + * The compiler can treat static final fields as compile-time constants + * and can constant-fold them while (final or not) local variables are + * run time values. + */ + + static final Unsafe theUnsafe; + + /** The offset to the first element in a byte array. */ + static final int BYTE_ARRAY_BASE_OFFSET; + + static { + theUnsafe = (Unsafe) AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return f.get(null); + } catch (NoSuchFieldException e) { + // It doesn't matter what we throw; + // it's swallowed in getBestComparator(). + throw new Error(); + } catch (IllegalAccessException e) { + throw new Error(); + } + } + }); + + BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); + + // sanity check - this should never fail + if (theUnsafe.arrayIndexScale(byte[].class) != 1) { + throw new AssertionError(); + } + } + + @Override public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + int minWords = minLength / Longs.BYTES; + + /* + * Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a + * time is no slower than comparing 4 bytes at a time even on 32-bit. + * On the other hand, it is substantially faster on 64-bit. + */ + for (int i = 0; i < minWords * Longs.BYTES; i += Longs.BYTES) { + long lw = theUnsafe.getLong(left, BYTE_ARRAY_BASE_OFFSET + (long) i); + long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); + long diff = lw ^ rw; + + if (diff != 0) { + if (!littleEndian) { + return UnsignedLongs.compare(lw, rw); + } + + // Use binary search + int n = 0; + int y; + int x = (int) diff; + if (x == 0) { + x = (int) (diff >>> 32); + n = 32; + } + + y = x << 16; + if (y == 0) { + n += 16; + } else { + x = y; + } + + y = x << 8; + if (y == 0) { + n += 8; + } + return (int) (((lw >>> n) & UNSIGNED_MASK) - ((rw >>> n) & UNSIGNED_MASK)); + } + } + + // The epilogue to cover the last (minLength % 8) elements. + for (int i = minWords * Longs.BYTES; i < minLength; i++) { + int result = UnsignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + enum PureJavaComparator implements Comparator { + INSTANCE; + + @Override public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = UnsignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + } + + /** + * Returns the Unsafe-using Comparator, or falls back to the pure-Java + * implementation if unable to do so. + */ + static Comparator getBestComparator() { + try { + Class theClass = Class.forName(UNSAFE_COMPARATOR_NAME); + + // yes, UnsafeComparator does implement Comparator + @SuppressWarnings("unchecked") + Comparator comparator = + (Comparator) theClass.getEnumConstants()[0]; + return comparator; + } catch (Throwable t) { // ensure we really catch *everything* + return lexicographicalComparatorJavaImpl(); + } + } + } +} diff --git a/guava/src/com/google/common/primitives/UnsignedInteger.java b/guava/src/com/google/common/primitives/UnsignedInteger.java new file mode 100644 index 0000000..a0df3a9 --- /dev/null +++ b/guava/src/com/google/common/primitives/UnsignedInteger.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.primitives.UnsignedInts.INT_MASK; +import static com.google.common.primitives.UnsignedInts.compare; +import static com.google.common.primitives.UnsignedInts.toLong; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.math.BigInteger; + +import javax.annotation.Nullable; + +/** + * A wrapper class for unsigned {@code int} values, supporting arithmetic operations. + * + *

    In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. + * + *

    See the Guava User Guide article on + * unsigned primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class UnsignedInteger extends Number implements Comparable { + public static final UnsignedInteger ZERO = asUnsigned(0); + public static final UnsignedInteger ONE = asUnsigned(1); + public static final UnsignedInteger MAX_VALUE = asUnsigned(-1); + + private final int value; + + private UnsignedInteger(int value) { + this.value = value & 0xffffffff; + } + + /** + * Returns an {@code UnsignedInteger} that, when treated as signed, is + * equal to {@code value}. + */ + public static UnsignedInteger asUnsigned(int value) { + return new UnsignedInteger(value); + } + + /** + * Returns an {@code UnsignedInteger} that is equal to {@code value}, + * if possible. The inverse operation of {@link #longValue()}. + */ + public static UnsignedInteger valueOf(long value) { + checkArgument((value & INT_MASK) == value, + "value (%s) is outside the range for an unsigned integer value", value); + return asUnsigned((int) value); + } + + /** + * Returns a {@code UnsignedInteger} representing the same value as the specified + * {@link BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} + */ + public static UnsignedInteger valueOf(BigInteger value) { + checkNotNull(value); + checkArgument(value.signum() >= 0 && value.bitLength() <= Integer.SIZE, + "value (%s) is outside the range for an unsigned integer value", value); + return asUnsigned(value.intValue()); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed + * as an unsigned {@code int} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed + * as an unsigned {@code int} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string, int radix) { + return asUnsigned(UnsignedInts.parseUnsignedInt(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, + * returns the low 32 bits of the result. + */ + public UnsignedInteger add(UnsignedInteger val) { + checkNotNull(val); + return asUnsigned(this.value + val.value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would be negative, + * returns the low 32 bits of the result. + */ + public UnsignedInteger subtract(UnsignedInteger val) { + checkNotNull(val); + return asUnsigned(this.value - val.value); + } + + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 32 + * bits, returns the low 32 bits of the result. + */ + @GwtIncompatible("Does not truncate correctly") + public UnsignedInteger multiply(UnsignedInteger val) { + checkNotNull(val); + return asUnsigned(value * val.value); + } + + /** + * Returns the result of dividing this by {@code val}. + */ + public UnsignedInteger divide(UnsignedInteger val) { + checkNotNull(val); + return asUnsigned(UnsignedInts.divide(value, val.value)); + } + + /** + * Returns the remainder of dividing this by {@code val}. + */ + public UnsignedInteger remainder(UnsignedInteger val) { + checkNotNull(val); + return asUnsigned(UnsignedInts.remainder(value, val.value)); + } + + /** + * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse + * operation to {@link #asUnsigned}. + * + *

    Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value + * will be equal to {@code this - 2^32}. + */ + @Override + public int intValue() { + return value; + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code long}. + */ + @Override + public long longValue() { + return toLong(value); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + return longValue(); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + return longValue(); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. + */ + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(longValue()); + } + + /** + * Compares this unsigned integer to another unsigned integer. + * Returns {@code 0} if they are equal, a negative number if {@code this < other}, + * and a positive number if {@code this > other}. + */ + @Override + public int compareTo(UnsignedInteger other) { + checkNotNull(other); + return compare(value, other.value); + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof UnsignedInteger) { + UnsignedInteger other = (UnsignedInteger) obj; + return value == other.value; + } + return false; + } + + /** + * Returns a string representation of the {@code UnsignedInteger} value, in base 10. + */ + @Override + public String toString() { + return toString(10); + } + + /** + * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. + * If {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix + * {@code 10} is used. + */ + public String toString(int radix) { + return UnsignedInts.toString(value, radix); + } +} diff --git a/guava/src/com/google/common/primitives/UnsignedInts.java b/guava/src/com/google/common/primitives/UnsignedInts.java new file mode 100644 index 0000000..93ec66e --- /dev/null +++ b/guava/src/com/google/common/primitives/UnsignedInts.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code int} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value + * {@code 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well + * as signed versions of methods for which signedness is an issue. + * + *

    In addition, this class provides several static methods for converting an {@code int} to a + * {@code String} and a {@code String} to an {@code int} that treat the {@code int} as an unsigned + * number. + * + *

    Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code int} values. When possible, it is recommended that the {@link UnsignedInteger} wrapper + * class be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

    See the Guava User Guide article on + * unsigned primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible +public final class UnsignedInts { + static final long INT_MASK = 0xffffffffL; + + private UnsignedInts() {} + + static int flip(int value) { + return value ^ Integer.MIN_VALUE; + } + + /** + * Compares the two specified {@code int} values, treating them as unsigned values between + * {@code 0} and {@code 2^32 - 1} inclusive. + * + * @param a the first unsigned {@code int} to compare + * @param b the second unsigned {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(int a, int b) { + return Ints.compare(flip(a), flip(b)); + } + + /** + * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. + */ + public static long toLong(int value) { + return value & INT_MASK; + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code int} values separated by + * {@code separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting + * string (but not at the start or end) + * @param array an array of unsigned {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code int} values lexicographically. + * That is, it compares, using {@link #compare(int, int)}), the first pair of values that follow + * any common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [1] < [1, 2] < [2] < [1 << 31]}. + * + *

    The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + * + * @see Lexicographical order + * article at Wikipedia + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedInts.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int divide(int dividend, int divisor) { + return (int) (toLong(dividend) / toLong(divisor)); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int remainder(int dividend, int divisor) { + return (int) (toLong(dividend) % toLong(divisor)); + } + + /** + * Returns the unsigned {@code int} value represented by the given string. + * + * Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

      + *
    • {@code 0x}HexDigits + *
    • {@code 0X}HexDigits + *
    • {@code #}HexDigits + *
    • {@code 0}OctalDigits + *
    + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} + * value + * @since 13.0 + */ + public static int decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedInt(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /** + * Returns the unsigned {@code int} value represented by the given decimal string. + * + * @throws NumberFormatException if the string does not contain a valid unsigned integer, or if + * the value represented is too large to fit in an unsigned {@code int}. + * @throws NullPointerException if {@code s} is null + */ + public static int parseUnsignedInt(String s) { + return parseUnsignedInt(s, 10); + } + + /** + * Returns the unsigned {@code int} value represented by a string with the given radix. + * + * @param string the string containing the unsigned integer representation to be parsed. + * @param radix the radix to use while parsing {@code s}; must be between + * {@link Character#MIN_RADIX} and {@link Character#MAX_RADIX}. + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int}, or + * if supplied radix is invalid. + */ + public static int parseUnsignedInt(String string, int radix) { + checkNotNull(string); + long result = Long.parseLong(string, radix); + if ((result & INT_MASK) != result) { + throw new NumberFormatException("Input " + string + " in base " + radix + + " is not in the range of an unsigned integer"); + } + return (int) result; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + */ + public static String toString(int x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated + * as unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(int x, int radix) { + long asLong = x & INT_MASK; + return Long.toString(asLong, radix); + } +} diff --git a/guava/src/com/google/common/primitives/UnsignedLong.java b/guava/src/com/google/common/primitives/UnsignedLong.java new file mode 100644 index 0000000..1562497 --- /dev/null +++ b/guava/src/com/google/common/primitives/UnsignedLong.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.math.BigInteger; + +import javax.annotation.Nullable; + +/** + * A wrapper class for unsigned {@code long} values, supporting arithmetic operations. + * + *

    In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code long} values as unsigned, using the methods from {@link UnsignedLongs}. + * + *

    See the Guava User Guide article on + * unsigned primitive utilities. + * + * @author Louis Wasserman + * @author Colin Evans + * @since 11.0 + */ +@Beta +@GwtCompatible(serializable = true) +public final class UnsignedLong extends Number implements Comparable, Serializable { + + private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; + + public static final UnsignedLong ZERO = new UnsignedLong(0); + public static final UnsignedLong ONE = new UnsignedLong(1); + public static final UnsignedLong MAX_VALUE = new UnsignedLong(-1L); + + private final long value; + + private UnsignedLong(long value) { + this.value = value; + } + + /** + * Returns an {@code UnsignedLong} that, when treated as signed, is equal to {@code value}. The + * inverse operation is {@link #longValue()}. + * + *

    Put another way, if {@code value} is negative, the returned result will be equal to + * {@code 2^64 + value}; otherwise, the returned result will be equal to {@code value}. + */ + public static UnsignedLong asUnsigned(long value) { + return new UnsignedLong(value); + } + + /** + * Returns a {@code UnsignedLong} representing the same value as the specified {@code BigInteger} + * . This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^64} + */ + public static UnsignedLong valueOf(BigInteger value) { + checkNotNull(value); + checkArgument(value.signum() >= 0 && value.bitLength() <= Long.SIZE, + "value (%s) is outside the range for an unsigned long value", value); + return asUnsigned(value.longValue()); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as + * an unsigned {@code long} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value + */ + public static UnsignedLong valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as + * an unsigned {@code long} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value, or {@code radix} is not between {@link Character#MIN_RADIX} and + * {@link Character#MAX_RADIX} + */ + public static UnsignedLong valueOf(String string, int radix) { + return asUnsigned(UnsignedLongs.parseUnsignedLong(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 64 bits, + * returns the low 64 bits of the result. + */ + public UnsignedLong add(UnsignedLong val) { + checkNotNull(val); + return asUnsigned(this.value + val.value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would be negative, + * returns the low 64 bits of the result. + */ + public UnsignedLong subtract(UnsignedLong val) { + checkNotNull(val); + return asUnsigned(this.value - val.value); + } + + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 64 + * bits, returns the low 64 bits of the result. + */ + public UnsignedLong multiply(UnsignedLong val) { + checkNotNull(val); + return asUnsigned(value * val.value); + } + + /** + * Returns the result of dividing this by {@code val}. + */ + public UnsignedLong divide(UnsignedLong val) { + checkNotNull(val); + return asUnsigned(UnsignedLongs.divide(value, val.value)); + } + + /** + * Returns the remainder of dividing this by {@code val}. + */ + public UnsignedLong remainder(UnsignedLong val) { + checkNotNull(val); + return asUnsigned(UnsignedLongs.remainder(value, val.value)); + } + + /** + * Returns the value of this {@code UnsignedLong} as an {@code int}. + */ + @Override + public int intValue() { + return (int) value; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code long}. This is an inverse operation + * to {@link #asUnsigned}. + * + *

    Note that if this {@code UnsignedLong} holds a value {@code >= 2^63}, the returned value + * will be equal to {@code this - 2^64}. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code float}, analogous to a widening + * primitive conversion from {@code long} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + @SuppressWarnings("cast") + float fValue = (float) (value & UNSIGNED_MASK); + if (value < 0) { + fValue += 0x1.0p63f; + } + return fValue; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code double}, analogous to a widening + * primitive conversion from {@code long} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + @SuppressWarnings("cast") + double dValue = (double) (value & UNSIGNED_MASK); + if (value < 0) { + dValue += 0x1.0p63; + } + return dValue; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@link BigInteger}. + */ + public BigInteger bigIntegerValue() { + BigInteger bigInt = BigInteger.valueOf(value & UNSIGNED_MASK); + if (value < 0) { + bigInt = bigInt.setBit(Long.SIZE - 1); + } + return bigInt; + } + + @Override + public int compareTo(UnsignedLong o) { + checkNotNull(o); + return UnsignedLongs.compare(value, o.value); + } + + @Override + public int hashCode() { + return Longs.hashCode(value); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof UnsignedLong) { + UnsignedLong other = (UnsignedLong) obj; + return value == other.value; + } + return false; + } + + /** + * Returns a string representation of the {@code UnsignedLong} value, in base 10. + */ + @Override + public String toString() { + return UnsignedLongs.toString(value); + } + + /** + * Returns a string representation of the {@code UnsignedLong} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix + * {@code 10} is used. + */ + public String toString(int radix) { + return UnsignedLongs.toString(value, radix); + } +} diff --git a/guava/src/com/google/common/primitives/UnsignedLongs.java b/guava/src/com/google/common/primitives/UnsignedLongs.java new file mode 100644 index 0000000..83f732f --- /dev/null +++ b/guava/src/com/google/common/primitives/UnsignedLongs.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code long} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value + * {@code 2^64 + x}). The methods for which signedness is not an issue are in {@link Longs}, as + * well as signed versions of methods for which signedness is an issue. + * + *

    In addition, this class provides several static methods for converting a {@code long} to a + * {@code String} and a {@code String} to a {@code long} that treat the {@code long} as an unsigned + * number. + * + *

    Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code long} values. When possible, it is recommended that the {@link UnsignedLong} wrapper + * class be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

    See the Guava User Guide article on + * unsigned primitive utilities. + * + * @author Louis Wasserman + * @author Brian Milch + * @author Colin Evans + * @since 10.0 + */ +@Beta +@GwtCompatible +public final class UnsignedLongs { + private UnsignedLongs() {} + + public static final long MAX_VALUE = -1L; // Equivalent to 2^64 - 1 + + /** + * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on + * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} + * as signed longs. + */ + private static long flip(long a) { + return a ^ Long.MIN_VALUE; + } + + /** + * Compares the two specified {@code long} values, treating them as unsigned values between + * {@code 0} and {@code 2^64 - 1} inclusive. + * + * @param a the first unsigned {@code long} to compare + * @param b the second unsigned {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(long a, long b) { + return Longs.compare(flip(a), flip(b)); + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code long} values separated by + * {@code separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting + * string (but not at the start or end) + * @param array an array of unsigned {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code long} values + * lexicographically. That is, it compares, using {@link #compare(long, long)}), the first pair of + * values that follow any common prefix, or when one array is a prefix of the other, treats the + * shorter array as the lesser. For example, {@code [] < [1L] < [1L, 2L] < [2L] < [1L << 63]}. + * + *

    The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with + * {@link Arrays#equals(long[], long[])}. + * + * @see Lexicographical order + * article at Wikipedia + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedLongs.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static long divide(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + * @since 11.0 + */ + public static long remainder(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return rem - (compare(rem, divisor) >= 0 ? divisor : 0); + } + + /** + * Returns the unsigned {@code long} value represented by the given decimal string. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + */ + public static long parseUnsignedLong(String s) { + return parseUnsignedLong(s, 10); + } + + /** + * Returns the unsigned {@code long} value represented by the given string. + * + * Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

      + *
    • {@code 0x}HexDigits + *
    • {@code 0X}HexDigits + *
    • {@code #}HexDigits + *
    • {@code 0}OctalDigits + *
    + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @since 13.0 + */ + public static long decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedLong(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /** + * Returns the unsigned {@code long} value represented by a string with the given radix. + * + * @param s the string containing the unsigned {@code long} representation to be parsed. + * @param radix the radix to use while parsing {@code s} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * with the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static long parseUnsignedLong(String s, int radix) { + checkNotNull(s); + if (s.length() == 0) { + throw new NumberFormatException("empty string"); + } + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException("illegal radix: " + radix); + } + + int max_safe_pos = maxSafeDigits[radix] - 1; + long value = 0; + for (int pos = 0; pos < s.length(); pos++) { + int digit = Character.digit(s.charAt(pos), radix); + if (digit == -1) { + throw new NumberFormatException(s); + } + if (pos > max_safe_pos && overflowInParse(value, digit, radix)) { + throw new NumberFormatException("Too large for unsigned long: " + s); + } + value = (value * radix) + digit; + } + + return value; + } + + /** + * Returns true if (current * radix) + digit is a number too large to be represented by an + * unsigned long. This is useful for detecting overflow while parsing a string representation of + * a number. Does not verify whether supplied radix is valid, passing an invalid radix will give + * undefined results or an ArrayIndexOutOfBoundsException. + */ + private static boolean overflowInParse(long current, int digit, int radix) { + if (current >= 0) { + if (current < maxValueDivs[radix]) { + return false; + } + if (current > maxValueDivs[radix]) { + return true; + } + // current == maxValueDivs[radix] + return (digit > maxValueMods[radix]); + } + + // current < 0: high bit is set + return true; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + */ + public static String toString(long x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated + * as unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(long x, int radix) { + checkArgument(radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", radix); + if (x == 0) { + // Simply return "0" + return "0"; + } else { + char[] buf = new char[64]; + int i = buf.length; + if (x < 0) { + // Separate off the last digit using unsigned division. That will leave + // a number that is nonnegative as a signed integer. + long quotient = divide(x, radix); + long rem = x - quotient * radix; + buf[--i] = Character.forDigit((int) rem, radix); + x = quotient; + } + // Simple modulo/division approach + while (x > 0) { + buf[--i] = Character.forDigit((int) (x % radix), radix); + x /= radix; + } + // Generate string + return new String(buf, i, buf.length - i); + } + } + + // calculated as 0xffffffffffffffff / radix + private static final long[] maxValueDivs = new long[Character.MAX_RADIX + 1]; + private static final int[] maxValueMods = new int[Character.MAX_RADIX + 1]; + private static final int[] maxSafeDigits = new int[Character.MAX_RADIX + 1]; + static { + BigInteger overflow = new BigInteger("10000000000000000", 16); + for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { + maxValueDivs[i] = divide(MAX_VALUE, i); + maxValueMods[i] = (int) remainder(MAX_VALUE, i); + maxSafeDigits[i] = overflow.toString(i).length() - 1; + } + } +} diff --git a/guava/src/com/google/common/primitives/package-info.java b/guava/src/com/google/common/primitives/package-info.java new file mode 100644 index 0000000..205183f --- /dev/null +++ b/guava/src/com/google/common/primitives/package-info.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Static utilities for working with the eight primitive types and {@code void}, + * and value types for treating them as unsigned. + * + *

    This package is a part of the open-source + * Guava libraries. + * + *

    See the Guava User Guide article on + * primitive utilities. + * + *

    Contents

    + * + *

    General static utilities

    + * + *
      + *
    • {@link com.google.common.primitives.Primitives} + *
    + * + *

    Per-type static utilities

    + * + *
      + *
    • {@link com.google.common.primitives.Booleans} + *
    • {@link com.google.common.primitives.Bytes} + *
        + *
      • {@link com.google.common.primitives.SignedBytes} + *
      • {@link com.google.common.primitives.UnsignedBytes} + *
      + *
    • {@link com.google.common.primitives.Chars} + *
    • {@link com.google.common.primitives.Doubles} + *
    • {@link com.google.common.primitives.Floats} + *
    • {@link com.google.common.primitives.Ints} + *
        + *
      • {@link com.google.common.primitives.UnsignedInts} + *
      + *
    • {@link com.google.common.primitives.Longs} + *
        + *
      • {@link com.google.common.primitives.UnsignedLongs} + *
      + *
    • {@link com.google.common.primitives.Shorts} + *
    + * + *

    Value types

    + *
      + *
    • {@link com.google.common.primitives.UnsignedInteger} + *
    • {@link com.google.common.primitives.UnsignedLong} + *
    + */ +@ParametersAreNonnullByDefault +package com.google.common.primitives; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/guava/src/com/google/common/reflect/AbstractInvocationHandler.java b/guava/src/com/google/common/reflect/AbstractInvocationHandler.java new file mode 100644 index 0000000..8453b7c --- /dev/null +++ b/guava/src/com/google/common/reflect/AbstractInvocationHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +import javax.annotation.Nullable; + +/** + * Abstract implementation of {@link InvocationHandler} that handles {@link Object#equals}, + * {@link Object#hashCode} and {@link Object#toString}. + * + * @author Ben Yu + * @since 12.0 + */ +@Beta +public abstract class AbstractInvocationHandler implements InvocationHandler { + + private static final Object[] NO_ARGS = {}; + + /** + * {@inheritDoc} + * + *

    {@link Object#equals}, {@link Object#hashCode} are implemented according to referential + * equality (the default behavior of {@link Object}). {@link Object#toString} delegates to + * {@link #toString} that can be overridden by subclasses. + */ + @Override public final Object invoke(Object proxy, Method method, @Nullable Object[] args) + throws Throwable { + if (args == null) { + args = NO_ARGS; + } + if (args.length == 0 && method.getName().equals("hashCode")) { + return System.identityHashCode(proxy); + } + if (args.length == 1 + && method.getName().equals("equals") + && method.getParameterTypes()[0] == Object.class) { + return proxy == args[0]; + } + if (args.length == 0 && method.getName().equals("toString")) { + return toString(); + } + return handleInvocation(proxy, method, args); + } + + /** + * {@link #invoke} delegates to this method upon any method invocation on the proxy instance, + * except {@link Object#equals}, {@link Object#hashCode} and {@link Object#toString}. The result + * will be returned as the proxied method's return value. + * + *

    Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter, + * an empty array is passed in. + */ + protected abstract Object handleInvocation(Object proxy, Method method, Object[] args) + throws Throwable; + + /** + * The dynamic proxies' {@link Object#toString} will delegate to this method. Subclasses can + * override this to provide custom string representation of the proxies. + */ + @Override public String toString() { + return super.toString(); + } +} diff --git a/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java b/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java new file mode 100644 index 0000000..43e5e1e --- /dev/null +++ b/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +/** + * A type-to-instance map backed by an {@link ImmutableMap}. See also {@link + * MutableTypeToInstanceMap}. + * + * @author Ben Yu + * @since 13.0 + */ +@Beta +public final class ImmutableTypeToInstanceMap extends ForwardingMap, B> + implements TypeToInstanceMap { + + /** Returns an empty type to instance map. */ + public static ImmutableTypeToInstanceMap of() { + return new ImmutableTypeToInstanceMap(ImmutableMap., B>of()); + } + + /** Returns a new builder. */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable type-to-instance maps. Example: + *

       {@code
    +   *
    +   *   static final ImmutableTypeToInstanceMap> HANDLERS =
    +   *       ImmutableTypeToInstanceMap.>builder()
    +   *           .put(new TypeToken>() {}, new FooHandler())
    +   *           .put(new TypeToken>() {}, new SubBarHandler())
    +   *           .build();}
    + * + * After invoking {@link #build()} it is still possible to add more entries + * and build again. Thus each map generated by this builder will be a superset + * of any map generated before it. + * + * @since 13.0 + */ + @Beta + public static final class Builder { + private final ImmutableMap.Builder, B> mapBuilder + = ImmutableMap.builder(); + + private Builder() {} + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(Class key, T value) { + mapBuilder.put(TypeToken.of(key), value); + return this; + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(TypeToken key, T value) { + mapBuilder.put(key.rejectTypeVariables(), value); + return this; + } + + /** + * Returns a new immutable type-to-instance map containing the entries + * provided to this builder. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableTypeToInstanceMap build() { + return new ImmutableTypeToInstanceMap(mapBuilder.build()); + } + } + + private final ImmutableMap, B> delegate; + + private ImmutableTypeToInstanceMap(ImmutableMap, B> delegate) { + this.delegate = delegate; + } + + @Override public T getInstance(TypeToken type) { + return trustedGet(type.rejectTypeVariables()); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public T putInstance(TypeToken type, T value) { + throw new UnsupportedOperationException(); + } + + @Override public T getInstance(Class type) { + return trustedGet(TypeToken.of(type)); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public T putInstance(Class type, T value) { + throw new UnsupportedOperationException(); + } + + @Override protected Map, B> delegate() { + return delegate; + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + private T trustedGet(TypeToken type) { + return (T) delegate.get(type); + } +} diff --git a/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java b/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java new file mode 100644 index 0000000..5f1249d --- /dev/null +++ b/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.Maps; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A mutable type-to-instance map. + * See also {@link ImmutableTypeToInstanceMap}. + * + * @author Ben Yu + * @since 13.0 + */ +@Beta +public final class MutableTypeToInstanceMap extends ForwardingMap, B> + implements TypeToInstanceMap { + + private final Map, B> backingMap = Maps.newHashMap(); + + @Nullable + @Override + public T getInstance(Class type) { + return trustedGet(TypeToken.of(type)); + } + + @Nullable + @Override + public T putInstance(Class type, @Nullable T value) { + return trustedPut(TypeToken.of(type), value); + } + + @Nullable + @Override + public T getInstance(TypeToken type) { + return trustedGet(type.rejectTypeVariables()); + } + + @Nullable + @Override + public T putInstance(TypeToken type, @Nullable T value) { + return trustedPut(type.rejectTypeVariables(), value); + } + + /** Not supported. Use {@link #putInstance} instead. */ + @Override public B put(TypeToken key, B value) { + throw new UnsupportedOperationException("Please use putInstance() instead."); + } + + /** Not supported. Use {@link #putInstance} instead. */ + @Override public void putAll(Map, ? extends B> map) { + throw new UnsupportedOperationException("Please use putInstance() instead."); + } + + @Override protected Map, B> delegate() { + return backingMap; + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + @Nullable + private T trustedPut(TypeToken type, @Nullable T value) { + return (T) backingMap.put(type, value); + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + @Nullable + private T trustedGet(TypeToken type) { + return (T) backingMap.get(type); + } +} diff --git a/guava/src/com/google/common/reflect/Reflection.java b/guava/src/com/google/common/reflect/Reflection.java new file mode 100644 index 0000000..6b25f01 --- /dev/null +++ b/guava/src/com/google/common/reflect/Reflection.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +/** + * Static utilities relating to Java reflection. + * + * @since 12.0 + */ +@Beta +public final class Reflection { + + /** + * Returns the package name of {@code cls} according to the Java Language Specification (section + * 6.7). Unlike {@link Class#getPackage}, this method only parses the class name, without + * attempting to define the {@link Package} and hence load files. + */ + public static String getPackageName(Class cls) { + return getPackageName(cls.getName()); + } + + /** + * Returns the package name of {@code classFullName} according to the Java Language Specification + * (section 6.7). Unlike {@link Class#getPackage}, this method only parses the class name, without + * attempting to define the {@link Package} and hence load files. + */ + public static String getPackageName(String classFullName) { + int lastDot = classFullName.lastIndexOf('.'); + if (lastDot < 0) { + return ""; + } else { + return classFullName.substring(0, lastDot); + } + } + + /** + * Ensures that the given classes are initialized, as described in + * + * JLS Section 12.4.2. + * + *

    WARNING: Normally it's a smell if a class needs to be explicitly initialized, because static + * state hurts system maintainability and testability. In cases when you have no choice while + * inter-operating with a legacy framework, this method helps to keep the code less ugly. + * + * @throws ExceptionInInitializerError if an exception is thrown during + * initialization of a class + */ + public static void initialize(Class... classes) { + for (Class clazz : classes) { + try { + Class.forName(clazz.getName(), true, clazz.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + } + + /** + * Returns a proxy instance that implements {@code interfaceType} by + * dispatching method invocations to {@code handler}. The class loader of + * {@code interfaceType} will be used to define the proxy class. To implement + * multiple interfaces or specify a class loader, use + * {@link Proxy#newProxyInstance}. + * + * @throws IllegalArgumentException if {@code interfaceType} does not specify + * the type of a Java interface + */ + public static T newProxy( + Class interfaceType, InvocationHandler handler) { + checkNotNull(interfaceType); + checkNotNull(handler); + checkArgument(interfaceType.isInterface()); + Object object = Proxy.newProxyInstance( + interfaceType.getClassLoader(), + new Class[] { interfaceType }, + handler); + return interfaceType.cast(object); + } + + private Reflection() {} +} diff --git a/guava/src/com/google/common/reflect/TypeCapture.java b/guava/src/com/google/common/reflect/TypeCapture.java new file mode 100644 index 0000000..c686661 --- /dev/null +++ b/guava/src/com/google/common/reflect/TypeCapture.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Captures the actual type of {@code T}. + * + * @author Ben Yu + */ +abstract class TypeCapture { + + /** Returns the captured type. */ + final Type capture() { + Type superclass = getClass().getGenericSuperclass(); + checkArgument(superclass instanceof ParameterizedType, + "%s isn't parameterized", superclass); + return ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } +} diff --git a/guava/src/com/google/common/reflect/TypeParameter.java b/guava/src/com/google/common/reflect/TypeParameter.java new file mode 100644 index 0000000..a6a46bc --- /dev/null +++ b/guava/src/com/google/common/reflect/TypeParameter.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +/** + * Captures a free type variable that can be used in {@link TypeToken#where}. + * For example:

       {@code
    + *
    + *   static  TypeToken> listOf(Class elementType) {
    + *     return new TypeToken>() {}
    + *         .where(new TypeParameter() {}, elementType);
    + *   }
    + * }
    + * + * @author Ben Yu + * @since 12.0 + */ +@Beta +public abstract class TypeParameter extends TypeCapture { + + final TypeVariable typeVariable; + + private TypeParameter(TypeVariable typeVariable) { + this.typeVariable = checkNotNull(typeVariable); + } + + protected TypeParameter() { + Type type = capture(); + checkArgument(type instanceof TypeVariable, "%s should be a type variable.", type); + this.typeVariable = (TypeVariable) type; + } + + @Override public final int hashCode() { + return typeVariable.hashCode(); + } + + @Override public final boolean equals(Object o) { + if (o instanceof TypeParameter) { + TypeParameter that = (TypeParameter) o; + return typeVariable.equals(that.typeVariable); + } + return false; + } + + @Override public String toString() { + return typeVariable.toString(); + } +} diff --git a/guava/src/com/google/common/reflect/TypeResolver.java b/guava/src/com/google/common/reflect/TypeResolver.java new file mode 100644 index 0000000..0c42ef5 --- /dev/null +++ b/guava/src/com/google/common/reflect/TypeResolver.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * An object of this class encapsulates type mappings from type variables. Mappings are established + * with {@link #where} and types are resolved using {@link #resolveType}. + * + *

    Note that usually type mappings are already implied by the static type hierarchy (for example, + * the {@code E} type variable declared by class {@code List} naturally maps to {@code String} in + * the context of {@code class MyStringList implements List}. In such case, prefer to use + * {@link TypeToken#resolveType} since it's simpler and more type safe. This class should only be + * used when the type mapping isn't implied by the static type hierarchy, but provided through other + * means such as an annotation or external configuration file. + * + * @author Ben Yu + */ +class TypeResolver { + + private final ImmutableMap, Type> typeTable; + + public TypeResolver() { + this.typeTable = ImmutableMap.of(); + } + + private TypeResolver(ImmutableMap, Type> typeTable) { + this.typeTable = typeTable; + } + + static TypeResolver accordingTo(Type type) { + return new TypeResolver().where(TypeMappingIntrospector.getTypeMappings(type)); + } + + /** + * Returns a new {@code TypeResolver} with type variables in {@code formal} mapping to types in + * {@code actual}. + * + *

    For example, if {@code formal} is a {@code TypeVariable T}, and {@code actual} is {@code + * String.class}, then {@code new TypeResolver().where(formal, actual)} will {@linkplain + * #resolveType resolve} {@code ParameterizedType List} to {@code List}, and resolve + * {@code Map} to {@code Map} etc. Similarly, {@code formal} and + * {@code actual} can be {@code Map} and {@code Map} respectively, or they + * can be {@code E[]} and {@code String[]} respectively, or even any arbitrary combination + * thereof. + * + * @param formal The type whose type variables or itself is mapped to other type(s). It's almost + * always a bug if {@code formal} isn't a type variable and contains no type variable. Make + * sure you are passing the two parameters in the right order. + * @param actual The type that the formal type variable(s) are mapped to. It can be or contain yet + * other type variables, in which case these type variables will be further resolved if + * corresponding mappings exist in the current {@code TypeResolver} instance. + */ + public final TypeResolver where(Type formal, Type actual) { + Map, Type> mappings = Maps.newHashMap(); + populateTypeMappings(mappings, formal, actual); + return where(mappings); + } + + /** Returns a new {@code TypeResolver} with {@code variable} mapping to {@code type}. */ + final TypeResolver where(Map, ? extends Type> mappings) { + ImmutableMap.Builder, Type> builder = ImmutableMap.builder(); + builder.putAll(typeTable); + for (Map.Entry, ? extends Type> mapping : mappings.entrySet()) { + TypeVariable variable = mapping.getKey(); + Type type = mapping.getValue(); + checkArgument(!variable.equals(type), "Type variable %s bound to itself", variable); + builder.put(variable, type); + } + return new TypeResolver(builder.build()); + } + + private static void populateTypeMappings( + Map, Type> mappings, Type from, Type to) { + if (from.equals(to)) { + return; + } + if (from instanceof TypeVariable) { + mappings.put((TypeVariable) from, to); + } else if (from instanceof GenericArrayType) { + populateTypeMappings(mappings, + ((GenericArrayType) from).getGenericComponentType(), + checkNonNullArgument(Types.getComponentType(to), "%s is not an array type.", to)); + } else if (from instanceof ParameterizedType) { + ParameterizedType fromParameterizedType = (ParameterizedType) from; + ParameterizedType toParameterizedType = expectArgument(ParameterizedType.class, to); + checkArgument(fromParameterizedType.getRawType().equals(toParameterizedType.getRawType()), + "Inconsistent raw type: %s vs. %s", from, to); + Type[] fromArgs = fromParameterizedType.getActualTypeArguments(); + Type[] toArgs = toParameterizedType.getActualTypeArguments(); + checkArgument(fromArgs.length == toArgs.length); + for (int i = 0; i < fromArgs.length; i++) { + populateTypeMappings(mappings, fromArgs[i], toArgs[i]); + } + } else if (from instanceof WildcardType) { + WildcardType fromWildcardType = (WildcardType) from; + WildcardType toWildcardType = expectArgument(WildcardType.class, to); + Type[] fromUpperBounds = fromWildcardType.getUpperBounds(); + Type[] toUpperBounds = toWildcardType.getUpperBounds(); + Type[] fromLowerBounds = fromWildcardType.getLowerBounds(); + Type[] toLowerBounds = toWildcardType.getLowerBounds(); + checkArgument( + fromUpperBounds.length == toUpperBounds.length + && fromLowerBounds.length == toLowerBounds.length, + "Incompatible type: %s vs. %s", from, to); + for (int i = 0; i < fromUpperBounds.length; i++) { + populateTypeMappings(mappings, fromUpperBounds[i], toUpperBounds[i]); + } + for (int i = 0; i < fromLowerBounds.length; i++) { + populateTypeMappings(mappings, fromLowerBounds[i], toLowerBounds[i]); + } + } else { + throw new IllegalArgumentException("No type mapping from " + from); + } + } + + /** + * Resolves all type variables in {@code type} and all downstream types and + * returns a corresponding type with type variables resolved. + */ + public final Type resolveType(Type type) { + if (type instanceof TypeVariable) { + return resolveTypeVariable((TypeVariable) type); + } else if (type instanceof ParameterizedType) { + return resolveParameterizedType((ParameterizedType) type); + } else if (type instanceof GenericArrayType) { + return resolveGenericArrayType((GenericArrayType) type); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + return new Types.WildcardTypeImpl( + resolveTypes(wildcardType.getLowerBounds()), + resolveTypes(wildcardType.getUpperBounds())); + } else { + // if Class, no resolution needed, we are done. + return type; + } + } + + private Type[] resolveTypes(Type[] types) { + Type[] result = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = resolveType(types[i]); + } + return result; + } + + private Type resolveGenericArrayType(GenericArrayType type) { + Type componentType = resolveType(type.getGenericComponentType()); + return Types.newArrayType(componentType); + } + + private Type resolveTypeVariable(final TypeVariable var) { + final TypeResolver unguarded = this; + TypeResolver guarded = new TypeResolver(typeTable) { + @Override Type resolveTypeVariable( + TypeVariable intermediateVar, TypeResolver guardedResolver) { + if (intermediateVar.getGenericDeclaration().equals(var.getGenericDeclaration())) { + return intermediateVar; + } + return unguarded.resolveTypeVariable(intermediateVar, guardedResolver); + } + }; + return resolveTypeVariable(var, guarded); + } + + /** + * Resolves {@code var} using the encapsulated type mapping. If it maps to yet another + * non-reified type, {@code guardedResolver} is used to do further resolution, which doesn't try + * to resolve any type variable on generic declarations that are already being resolved. + */ + Type resolveTypeVariable(TypeVariable var, TypeResolver guardedResolver) { + Type type = typeTable.get(var); + if (type == null) { + Type[] bounds = var.getBounds(); + if (bounds.length == 0) { + return var; + } + return Types.newTypeVariable( + var.getGenericDeclaration(), + var.getName(), + guardedResolver.resolveTypes(bounds)); + } + return guardedResolver.resolveType(type); // in case the type is yet another type variable. + } + + private ParameterizedType resolveParameterizedType(ParameterizedType type) { + Type owner = type.getOwnerType(); + Type resolvedOwner = (owner == null) ? null : resolveType(owner); + Type resolvedRawType = resolveType(type.getRawType()); + + Type[] vars = type.getActualTypeArguments(); + Type[] resolvedArgs = new Type[vars.length]; + for (int i = 0; i < vars.length; i++) { + resolvedArgs[i] = resolveType(vars[i]); + } + return Types.newParameterizedTypeWithOwner( + resolvedOwner, (Class) resolvedRawType, resolvedArgs); + } + + private static T checkNonNullArgument(T arg, String format, Object... messageParams) { + checkArgument(arg != null, format, messageParams); + return arg; + } + + private static T expectArgument(Class type, Object arg) { + try { + return type.cast(arg); + } catch (ClassCastException e) { + throw new IllegalArgumentException(arg + " is not a " + type.getSimpleName()); + } + } + + private static final class TypeMappingIntrospector { + + private static final WildcardCapturer wildcardCapturer = new WildcardCapturer(); + + private final Map, Type> mappings = Maps.newHashMap(); + private final Set introspectedTypes = Sets.newHashSet(); + + /** + * Returns type mappings using type parameters and type arguments found in + * the generic superclass and the super interfaces of {@code contextClass}. + */ + static ImmutableMap, Type> getTypeMappings( + Type contextType) { + TypeMappingIntrospector introspector = new TypeMappingIntrospector(); + introspector.introspect(wildcardCapturer.capture(contextType)); + return ImmutableMap.copyOf(introspector.mappings); + } + + private void introspect(Type type) { + if (!introspectedTypes.add(type)) { + return; + } + if (type instanceof ParameterizedType) { + introspectParameterizedType((ParameterizedType) type); + } else if (type instanceof Class) { + introspectClass((Class) type); + } else if (type instanceof TypeVariable) { + for (Type bound : ((TypeVariable) type).getBounds()) { + introspect(bound); + } + } else if (type instanceof WildcardType) { + for (Type bound : ((WildcardType) type).getUpperBounds()) { + introspect(bound); + } + } + } + + private void introspectClass(Class clazz) { + introspect(clazz.getGenericSuperclass()); + for (Type interfaceType : clazz.getGenericInterfaces()) { + introspect(interfaceType); + } + } + + private void introspectParameterizedType( + ParameterizedType parameterizedType) { + Class rawClass = (Class) parameterizedType.getRawType(); + TypeVariable[] vars = rawClass.getTypeParameters(); + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + checkState(vars.length == typeArgs.length); + for (int i = 0; i < vars.length; i++) { + map(vars[i], typeArgs[i]); + } + introspectClass(rawClass); + introspect(parameterizedType.getOwnerType()); + } + + private void map(final TypeVariable var, final Type arg) { + if (mappings.containsKey(var)) { + // Mapping already established + // This is possible when following both superClass -> enclosingClass + // and enclosingclass -> superClass paths. + // Since we follow the path of superclass first, enclosing second, + // superclass mapping should take precedence. + return; + } + // First, check whether var -> arg forms a cycle + for (Type t = arg; t != null; t = mappings.get(t)) { + if (var.equals(t)) { + // cycle detected, remove the entire cycle from the mapping so that + // each type variable resolves deterministically to itself. + // Otherwise, a F -> T cycle will end up resolving both F and T + // nondeterministically to either F or T. + for (Type x = arg; x != null; x = mappings.remove(x)) {} + return; + } + } + mappings.put(var, arg); + } + } + + // This is needed when resolving types against a context with wildcards + // For example: + // class Holder { + // void set(T data) {...} + // } + // Holder> should *not* resolve the set() method to set(List data). + // Instead, it should create a capture of the wildcard so that set() rejects any List. + private static final class WildcardCapturer { + + private final AtomicInteger id = new AtomicInteger(); + + Type capture(Type type) { + checkNotNull(type); + if (type instanceof Class) { + return type; + } + if (type instanceof TypeVariable) { + return type; + } + if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return Types.newArrayType(capture(arrayType.getGenericComponentType())); + } + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return Types.newParameterizedTypeWithOwner( + captureNullable(parameterizedType.getOwnerType()), + (Class) parameterizedType.getRawType(), + capture(parameterizedType.getActualTypeArguments())); + } + if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] lowerBounds = wildcardType.getLowerBounds(); + if (lowerBounds.length == 0) { // ? extends something changes to capture-of + Type[] upperBounds = wildcardType.getUpperBounds(); + String name = "capture#" + id.incrementAndGet() + "-of ? extends " + + Joiner.on('&').join(upperBounds); + return Types.newTypeVariable( + WildcardCapturer.class, name, wildcardType.getUpperBounds()); + } else { + // TODO(benyu): handle ? super T somehow. + return type; + } + } + throw new AssertionError("must have been one of the known types"); + } + + private Type captureNullable(@Nullable Type type) { + if (type == null) { + return null; + } + return capture(type); + } + + private Type[] capture(Type[] types) { + Type[] result = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = capture(types[i]); + } + return result; + } + } +} diff --git a/guava/src/com/google/common/reflect/TypeToInstanceMap.java b/guava/src/com/google/common/reflect/TypeToInstanceMap.java new file mode 100644 index 0000000..3b00820 --- /dev/null +++ b/guava/src/com/google/common/reflect/TypeToInstanceMap.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A map, each entry of which maps a {@link TypeToken} to an instance of that type. + * In addition to implementing {@code Map}, the additional type-safe operations + * {@link #putInstance} and {@link #getInstance} are available. + * + *

    Generally, implementations don't support {@link #put} and {@link #putAll} + * because there is no way to check an object at runtime to be an instance of a + * {@link TypeToken}. Instead, caller should use the type safe {@link #putInstance}. + * + *

    Also, if caller suppresses unchecked warnings and passes in an {@code Iterable} + * for type {@code Iterable}, the map won't be able to detect and throw type error. + * + *

    Like any other {@code Map}, this map may contain entries + * for primitive types, and a primitive type and its corresponding wrapper type + * may map to different values. + * + * @param the common supertype that all entries must share; often this is + * simply {@link Object} + * + * @author Ben Yu + * @since 13.0 + */ +@Beta +public interface TypeToInstanceMap extends Map, B> { + + /** + * Returns the value the specified class is mapped to, or {@code null} if no + * entry for this class is present. This will only return a value that was + * bound to this specific class, not a value that may have been bound to a + * subtype. + * + *

    {@code getInstance(Foo.class)} is equivalent to + * {@code getInstance(TypeToken.of(Foo.class))}. + */ + @Nullable + T getInstance(Class type); + + /** + * Maps the specified class to the specified value. Does not associate + * this value with any of the class's supertypes. + * + *

    {@code putInstance(Foo.class, foo)} is equivalent to + * {@code putInstance(TypeToken.of(Foo.class), foo)}. + * + * @return the value previously associated with this class (possibly {@code null}), + * or {@code null} if there was no previous entry. + */ + @Nullable + T putInstance(Class type, @Nullable T value); + + /** + * Returns the value the specified type is mapped to, or {@code null} if no + * entry for this type is present. This will only return a value that was + * bound to this specific type, not a value that may have been bound to a subtype. + */ + @Nullable + T getInstance(TypeToken type); + + /** + * Maps the specified type to the specified value. Does not associate + * this value with any of the type's supertypes. + * + * @return the value previously associated with this type (possibly {@code null}), + * or {@code null} if there was no previous entry. + */ + @Nullable + T putInstance(TypeToken type, @Nullable T value); +} diff --git a/guava/src/com/google/common/reflect/TypeToken.java b/guava/src/com/google/common/reflect/TypeToken.java new file mode 100644 index 0000000..08a2440 --- /dev/null +++ b/guava/src/com/google/common/reflect/TypeToken.java @@ -0,0 +1,1086 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ForwardingSet; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; + +import java.io.Serializable; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A {@link Type} with generics. + * + *

    Operations that are otherwise only available in {@link Class} are implemented to support + * {@code Type}, for example {@link #isAssignableFrom}, {@link #isArray} and {@link + * #getComponentType}. It also provides additional utilities such as {@link #getTypes} and {@link + * #resolveType} etc. + * + *

    There are three ways to get a {@code TypeToken} instance:

      + *
    • Wrap a {@code Type} obtained via reflection. For example: {@code + * TypeToken.of(method.getGenericReturnType())}. + *
    • Capture a generic type with a (usually anonymous) subclass. For example:
         {@code
      + *
      + *   new TypeToken>() {}
      + * }
      + * Note that it's critical that the actual type argument is carried by a subclass. + * The following code is wrong because it only captures the {@code } type variable + * of the {@code listType()} method signature; while {@code } is lost in erasure: + *
         {@code
      + *
      + *   class Util {
      + *     static  TypeToken> listType() {
      + *       return new TypeToken>() {};
      + *     }
      + *   }
      + *
      + *   TypeToken> stringListType = Util.listType();
      + * }
      + *
    • Capture a generic type with a (usually anonymous) subclass and resolve it against + * a context class that knows what the type parameters are. For example:
         {@code
      + *   abstract class IKnowMyType {
      + *     TypeToken type = new TypeToken(getClass()) {};
      + *   }
      + *   new IKnowMyType() {}.type => String
      + * }
      + *
    + * + *

    {@code TypeToken} is serializable when no type variable is contained in the type. + * + *

    Note to Guice users: {@code} TypeToken is similar to Guice's {@code TypeLiteral} class, + * but with one important difference: it supports non-reified types such as {@code T}, + * {@code List} or even {@code List}; while TypeLiteral does not. + * TypeToken is also serializable and offers numerous additional utility methods. + * + * @author Bob Lee + * @author Sven Mawson + * @author Ben Yu + * @since 12.0 + */ +@Beta +@SuppressWarnings("serial") // SimpleTypeToken is the serialized form. +public abstract class TypeToken extends TypeCapture implements Serializable { + + private final Type runtimeType; + + /** Resolver for resolving types with {@link #runtimeType} as context. */ + private transient TypeResolver typeResolver; + + /** + * Constructs a new type token of {@code T}. + * + *

    Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute + * it at runtime despite erasure. + * + *

    For example:

       {@code
    +   *
    +   *   TypeToken> t = new TypeToken>() {};
    +   * }
    + */ + protected TypeToken() { + this.runtimeType = capture(); + checkState(!(runtimeType instanceof TypeVariable), + "Cannot construct a TypeToken for a type variable.\n" + + "You probably meant to call new TypeToken<%s>(getClass()) " + + "that can resolve the type variable for you.\n" + + "If you do need to create a TypeToken of a type variable, " + + "please use TypeToken.of() instead.", runtimeType); + } + + /** + * Constructs a new type token of {@code T} while resolving free type variables in the context of + * {@code declaringClass}. + * + *

    Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute + * it at runtime despite erasure. + * + *

    For example:

       {@code
    +   *
    +   *   abstract class IKnowMyType {
    +   *     TypeToken getMyType() {
    +   *       return new TypeToken(getClass()) {};
    +   *     }
    +   *   }
    +   *
    +   *   new IKnowMyType() {}.getMyType() => String
    +   * }
    + */ + protected TypeToken(Class declaringClass) { + Type captured = super.capture(); + if (captured instanceof Class) { + this.runtimeType = captured; + } else { + this.runtimeType = of(declaringClass).resolveType(captured).runtimeType; + } + } + + private TypeToken(Type type) { + this.runtimeType = checkNotNull(type); + } + + /** Returns an instance of type token that wraps {@code type}. */ + public static TypeToken of(Class type) { + return new SimpleTypeToken(type); + } + + /** Returns an instance of type token that wraps {@code type}. */ + public static TypeToken of(Type type) { + return new SimpleTypeToken(type); + } + + /** + * Returns the raw type of {@code T}. Formally speaking, if {@code T} is returned by + * {@link java.lang.reflect.Method#getGenericReturnType}, the raw type is what's returned by + * {@link java.lang.reflect.Method#getReturnType} of the same method object. Specifically: + *
      + *
    • If {@code T} is a {@code Class} itself, {@code T} itself is returned. + *
    • If {@code T} is a {@link ParameterizedType}, the raw type of the parameterized type is + * returned. + *
    • If {@code T} is a {@link GenericArrayType}, the returned type is the corresponding array + * class. For example: {@code List[] => List[]}. + *
    • If {@code T} is a type variable or a wildcard type, the raw type of the first upper bound + * is returned. For example: {@code => Foo}. + *
    + */ + public final Class getRawType() { + Class rawType = getRawType(runtimeType); + @SuppressWarnings("unchecked") // raw type is |T| + Class result = (Class) rawType; + return result; + } + + /** + * Returns the raw type of the class or parameterized type; if {@code T} is type variable or + * wildcard type, the raw types of all its upper bounds are returned. + */ + private ImmutableSet> getImmediateRawTypes() { + // Cast from ImmutableSet> to ImmutableSet> + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableSet> result = (ImmutableSet) getRawTypes(runtimeType); + return result; + } + + /** Returns the represented type. */ + public final Type getType() { + return runtimeType; + } + + /** + * Returns a new {@code TypeToken} where type variables represented by {@code typeParam} + * are substituted by {@code typeArg}. For example, it can be used to construct + * {@code Map} for any {@code K} and {@code V} type:
       {@code
    +   *
    +   *   static  TypeToken> mapOf(
    +   *       TypeToken keyType, TypeToken valueType) {
    +   *     return new TypeToken>() {}
    +   *         .where(new TypeParameter() {}, keyType)
    +   *         .where(new TypeParameter() {}, valueType);
    +   *   }
    +   * }
    + * + * @param The parameter type + * @param typeParam the parameter type variable + * @param typeArg the actual type to substitute + */ + public final TypeToken where(TypeParameter typeParam, TypeToken typeArg) { + TypeResolver resolver = new TypeResolver() + .where(ImmutableMap.of(typeParam.typeVariable, typeArg.runtimeType)); + // If there's any type error, we'd report now rather than later. + return new SimpleTypeToken(resolver.resolveType(runtimeType)); + } + + /** + * Returns a new {@code TypeToken} where type variables represented by {@code typeParam} + * are substituted by {@code typeArg}. For example, it can be used to construct + * {@code Map} for any {@code K} and {@code V} type:
       {@code
    +   *
    +   *   static  TypeToken> mapOf(
    +   *       Class keyType, Class valueType) {
    +   *     return new TypeToken>() {}
    +   *         .where(new TypeParameter() {}, keyType)
    +   *         .where(new TypeParameter() {}, valueType);
    +   *   }
    +   * }
    + * + * @param The parameter type + * @param typeParam the parameter type variable + * @param typeArg the actual type to substitute + */ + public final TypeToken where(TypeParameter typeParam, Class typeArg) { + return where(typeParam, of(typeArg)); + } + + /** + * Resolves the given {@code type} against the type context represented by this type. + * For example:
       {@code
    +   *
    +   *   new TypeToken>() {}.resolveType(
    +   *       List.class.getMethod("get", int.class).getGenericReturnType())
    +   *   => String.class
    +   * }
    + */ + public final TypeToken resolveType(Type type) { + checkNotNull(type); + TypeResolver resolver = typeResolver; + if (resolver == null) { + resolver = (typeResolver = TypeResolver.accordingTo(runtimeType)); + } + return of(resolver.resolveType(type)); + } + + private TypeToken resolveSupertype(Type type) { + TypeToken supertype = resolveType(type); + // super types' type mapping is a subset of type mapping of this type. + supertype.typeResolver = typeResolver; + return supertype; + } + + /** + * Returns the generic superclass of this type or {@code null} if the type represents + * {@link Object} or an interface. This method is similar but different from {@link + * Class#getGenericSuperclass}. For example, {@code + * new TypeToken() {}.getGenericSuperclass()} will return {@code + * new TypeToken>() {}}; while {@code + * StringArrayList.class.getGenericSuperclass()} will return {@code ArrayList}, where {@code E} + * is the type variable declared by class {@code ArrayList}. + * + *

    If this type is a type variable or wildcard, its first upper bound is examined and returned + * if the bound is a class or extends from a class. This means that the returned type could be a + * type variable too. + */ + @Nullable + final TypeToken getGenericSuperclass() { + if (runtimeType instanceof TypeVariable) { + // First bound is always the super class, if one exists. + return boundAsSuperclass(((TypeVariable) runtimeType).getBounds()[0]); + } + if (runtimeType instanceof WildcardType) { + // wildcard has one and only one upper bound. + return boundAsSuperclass(((WildcardType) runtimeType).getUpperBounds()[0]); + } + Type superclass = getRawType().getGenericSuperclass(); + if (superclass == null) { + return null; + } + @SuppressWarnings("unchecked") // super class of T + TypeToken superToken = (TypeToken) resolveSupertype(superclass); + return superToken; + } + + @Nullable private TypeToken boundAsSuperclass(Type bound) { + TypeToken token = of(bound); + if (token.getRawType().isInterface()) { + return null; + } + @SuppressWarnings("unchecked") // only upper bound of T is passed in. + TypeToken superclass = (TypeToken) token; + return superclass; + } + + /** + * Returns the generic interfaces that this type directly {@code implements}. This method is + * similar but different from {@link Class#getGenericInterfaces()}. For example, {@code + * new TypeToken>() {}.getGenericInterfaces()} will return a list that contains + * {@code new TypeToken>() {}}; while {@code List.class.getGenericInterfaces()} + * will return an array that contains {@code Iterable}, where the {@code T} is the type + * variable declared by interface {@code Iterable}. + * + *

    If this type is a type variable or wildcard, its upper bounds are examined and those that + * are either an interface or upper-bounded only by interfaces are returned. This means that the + * returned types could include type variables too. + */ + final ImmutableList> getGenericInterfaces() { + if (runtimeType instanceof TypeVariable) { + return boundsAsInterfaces(((TypeVariable) runtimeType).getBounds()); + } + if (runtimeType instanceof WildcardType) { + return boundsAsInterfaces(((WildcardType) runtimeType).getUpperBounds()); + } + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Type interfaceType : getRawType().getGenericInterfaces()) { + @SuppressWarnings("unchecked") // interface of T + TypeToken resolvedInterface = (TypeToken) + resolveSupertype(interfaceType); + builder.add(resolvedInterface); + } + return builder.build(); + } + + private ImmutableList> boundsAsInterfaces(Type[] bounds) { + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Type bound : bounds) { + @SuppressWarnings("unchecked") // upper bound of T + TypeToken boundType = (TypeToken) of(bound); + if (boundType.getRawType().isInterface()) { + builder.add(boundType); + } + } + return builder.build(); + } + + /** + * Returns the set of interfaces and classes that this type is or is a subtype of. The returned + * types are parameterized with proper type arguments. + * + *

    Subtypes are always listed before supertypes. But the reverse is not true. A type isn't + * necessarily a subtype of all the types following. Order between types without subtype + * relationship is arbitrary and not guaranteed. + * + *

    If this type is a type variable or wildcard, upper bounds that are themselves type variables + * aren't included (their super interfaces and superclasses are). + */ + public final TypeSet getTypes() { + return new TypeSet(); + } + + /** + * Returns the generic form of {@code superclass}. For example, if this is + * {@code ArrayList}, {@code Iterable} is returned given the + * input {@code Iterable.class}. + */ + public final TypeToken getSupertype(Class superclass) { + checkArgument(superclass.isAssignableFrom(getRawType()), + "%s is not a super class of %s", superclass, this); + if (runtimeType instanceof TypeVariable) { + return getSupertypeFromUpperBounds(superclass, ((TypeVariable) runtimeType).getBounds()); + } + if (runtimeType instanceof WildcardType) { + return getSupertypeFromUpperBounds(superclass, ((WildcardType) runtimeType).getUpperBounds()); + } + if (superclass.isArray()) { + return getArraySupertype(superclass); + } + @SuppressWarnings("unchecked") // resolved supertype + TypeToken supertype = (TypeToken) + resolveSupertype(toGenericType(superclass).runtimeType); + return supertype; + } + + /** + * Returns subtype of {@code this} with {@code subclass} as the raw class. + * For example, if this is {@code Iterable} and {@code subclass} is {@code List}, + * {@code List} is returned. + */ + public final TypeToken getSubtype(Class subclass) { + checkArgument(!(runtimeType instanceof TypeVariable), + "Cannot get subtype of type variable <%s>", this); + if (runtimeType instanceof WildcardType) { + return getSubtypeFromLowerBounds(subclass, ((WildcardType) runtimeType).getLowerBounds()); + } + checkArgument(getRawType().isAssignableFrom(subclass), + "%s isn't a subclass of %s", subclass, this); + // unwrap array type if necessary + if (isArray()) { + return getArraySubtype(subclass); + } + @SuppressWarnings("unchecked") // guarded by the isAssignableFrom() statement above + TypeToken subtype = (TypeToken) + of(resolveTypeArgsForSubclass(subclass)); + return subtype; + } + + /** Returns true if this type is assignable from the given {@code type}. */ + public final boolean isAssignableFrom(TypeToken type) { + return isAssignableFrom(type.runtimeType); + } + + /** Check if this type is assignable from the given {@code type}. */ + public final boolean isAssignableFrom(Type type) { + return isAssignable(checkNotNull(type), runtimeType); + } + + /** + * Returns true if this type is known to be an array type, such as {@code int[]}, {@code T[]}, + * {@code []>} etc. + */ + public final boolean isArray() { + return getComponentType() != null; + } + + /** + * Returns the array component type if this type represents an array ({@code int[]}, {@code T[]}, + * {@code []>} etc.), or else {@code null} is returned. + */ + @Nullable public final TypeToken getComponentType() { + Type componentType = Types.getComponentType(runtimeType); + if (componentType == null) { + return null; + } + return of(componentType); + } + + /** + * The set of interfaces and classes that {@code T} is or is a subtype of. {@link Object} is not + * included in the set if this type is an interface. + */ + public class TypeSet extends ForwardingSet> implements Serializable { + + private transient ImmutableSet> types; + + TypeSet() {} + + /** Returns the types that are interfaces implemented by this type. */ + public TypeSet interfaces() { + return new InterfaceSet(this); + } + + /** Returns the types that are classes. */ + public TypeSet classes() { + return new ClassSet(); + } + + @Override protected Set> delegate() { + ImmutableSet> filteredTypes = types; + if (filteredTypes == null) { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = (ImmutableList) + TypeCollector.FOR_GENERIC_TYPE.collectTypes(TypeToken.this); + return (types = FluentIterable.from(collectedTypes) + .filter(TypeFilter.IGNORE_TYPE_VARIABLE_OR_WILDCARD) + .toImmutableSet()); + } else { + return filteredTypes; + } + } + + /** Returns the raw types of the types in this set, in the same order. */ + public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = (ImmutableList) + TypeCollector.FOR_RAW_TYPE.collectTypes(getImmediateRawTypes()); + return ImmutableSet.copyOf(collectedTypes); + } + + private static final long serialVersionUID = 0; + } + + private final class InterfaceSet extends TypeSet { + + private transient final TypeSet allTypes; + private transient ImmutableSet> interfaces; + + InterfaceSet(TypeSet allTypes) { + this.allTypes = allTypes; + } + + @Override protected Set> delegate() { + ImmutableSet> result = interfaces; + if (result == null) { + return (interfaces = FluentIterable.from(allTypes) + .filter(TypeFilter.INTERFACE_ONLY) + .toImmutableSet()); + } else { + return result; + } + } + + @Override public TypeSet interfaces() { + return this; + } + + @Override public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = (ImmutableList) + TypeCollector.FOR_RAW_TYPE.collectTypes(getImmediateRawTypes()); + return FluentIterable.from(collectedTypes) + .filter(new Predicate>() { + @Override public boolean apply(Class type) { + return type.isInterface(); + } + }) + .toImmutableSet(); + } + + @Override public TypeSet classes() { + throw new UnsupportedOperationException("interfaces().classes() not supported."); + } + + private Object readResolve() { + return getTypes().interfaces(); + } + + private static final long serialVersionUID = 0; + } + + private final class ClassSet extends TypeSet { + + private transient ImmutableSet> classes; + + @Override protected Set> delegate() { + ImmutableSet> result = classes; + if (result == null) { + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = (ImmutableList) + TypeCollector.FOR_GENERIC_TYPE.classesOnly().collectTypes(TypeToken.this); + return (classes = FluentIterable.from(collectedTypes) + .filter(TypeFilter.IGNORE_TYPE_VARIABLE_OR_WILDCARD) + .toImmutableSet()); + } else { + return result; + } + } + + @Override public TypeSet classes() { + return this; + } + + @Override public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = (ImmutableList) + TypeCollector.FOR_RAW_TYPE.classesOnly().collectTypes(getImmediateRawTypes()); + return ImmutableSet.copyOf(collectedTypes); + } + + @Override public TypeSet interfaces() { + throw new UnsupportedOperationException("classes().interfaces() not supported."); + } + + private Object readResolve() { + return getTypes().classes(); + } + + private static final long serialVersionUID = 0; + } + + private enum TypeFilter implements Predicate> { + + IGNORE_TYPE_VARIABLE_OR_WILDCARD { + @Override public boolean apply(TypeToken type) { + return !(type.runtimeType instanceof TypeVariable + || type.runtimeType instanceof WildcardType); + } + }, + INTERFACE_ONLY { + @Override public boolean apply(TypeToken type) { + return type.getRawType().isInterface(); + } + } + } + + /** + * Returns true if {@code o} is another {@code TypeToken} that represents the same {@link Type}. + */ + @Override public boolean equals(@Nullable Object o) { + if (o instanceof TypeToken) { + TypeToken that = (TypeToken) o; + return runtimeType.equals(that.runtimeType); + } + return false; + } + + @Override public int hashCode() { + return runtimeType.hashCode(); + } + + @Override public String toString() { + return Types.toString(runtimeType); + } + + /** Implemented to support serialization of subclasses. */ + protected Object writeReplace() { + // TypeResolver just transforms the type to our own impls that are Serializable + // except TypeVariable. + return of(new TypeResolver().resolveType(runtimeType)); + } + + /** + * Ensures that this type token doesn't contain type variables, which can cause unchecked type + * errors for callers like {@link TypeToInstanceMap}. + */ + final TypeToken rejectTypeVariables() { + checkArgument(!Types.containsTypeVariable(runtimeType), + "%s contains a type variable and is not safe for the operation"); + return this; + } + + private static boolean isAssignable(Type from, Type to) { + if (to.equals(from)) { + return true; + } + if (to instanceof WildcardType) { + return isAssignableToWildcardType(from, (WildcardType) to); + } + // if "from" is type variable, it's assignable if any of its "extends" + // bounds is assignable to "to". + if (from instanceof TypeVariable) { + return isAssignableFromAny(((TypeVariable) from).getBounds(), to); + } + // if "from" is wildcard, it'a assignable to "to" if any of its "extends" + // bounds is assignable to "to". + if (from instanceof WildcardType) { + return isAssignableFromAny(((WildcardType) from).getUpperBounds(), to); + } + if (from instanceof GenericArrayType) { + return isAssignableFromGenericArrayType((GenericArrayType) from, to); + } + // Proceed to regular Type assignability check + if (to instanceof Class) { + return isAssignableToClass(from, (Class) to); + } else if (to instanceof ParameterizedType) { + return isAssignableToParameterizedType(from, (ParameterizedType) to); + } else if (to instanceof GenericArrayType) { + return isAssignableToGenericArrayType(from, (GenericArrayType) to); + } else { // to instanceof TypeVariable + return false; + } + } + + private static boolean isAssignableFromAny(Type[] fromTypes, Type to) { + for (Type from : fromTypes) { + if (isAssignable(from, to)) { + return true; + } + } + return false; + } + + private static boolean isAssignableToClass(Type from, Class to) { + return to.isAssignableFrom(getRawType(from)); + } + + private static boolean isAssignableToWildcardType( + Type from, WildcardType to) { + // if "to" is , "from" can be: + // Foo, SubFoo, , , or + // . + // if "to" is , "from" can be: + // Foo, SuperFoo, or . + return isAssignable(from, supertypeBound(to)) && isAssignableBySubtypeBound(from, to); + } + + private static boolean isAssignableBySubtypeBound(Type from, WildcardType to) { + Type toSubtypeBound = subtypeBound(to); + if (toSubtypeBound == null) { + return true; + } + Type fromSubtypeBound = subtypeBound(from); + if (fromSubtypeBound == null) { + return false; + } + return isAssignable(toSubtypeBound, fromSubtypeBound); + } + + private static boolean isAssignableToParameterizedType(Type from, ParameterizedType to) { + Class matchedClass = getRawType(to); + if (!matchedClass.isAssignableFrom(getRawType(from))) { + return false; + } + Type[] typeParams = matchedClass.getTypeParameters(); + Type[] toTypeArgs = to.getActualTypeArguments(); + TypeToken fromTypeToken = of(from); + for (int i = 0; i < typeParams.length; i++) { + // If "to" is "List" + // and "from" is StringArrayList, + // First step is to figure out StringArrayList "is-a" List and is + // String. + // typeParams[0] is E and fromTypeToken.get(typeParams[0]) will resolve to + // String. + // String is then matched against . + Type fromTypeArg = fromTypeToken.resolveType(typeParams[i]).runtimeType; + if (!matchTypeArgument(fromTypeArg, toTypeArgs[i])) { + return false; + } + } + return true; + } + + private static boolean isAssignableToGenericArrayType(Type from, GenericArrayType to) { + if (from instanceof Class) { + Class fromClass = (Class) from; + if (!fromClass.isArray()) { + return false; + } + return isAssignable(fromClass.getComponentType(), to.getGenericComponentType()); + } else if (from instanceof GenericArrayType) { + GenericArrayType fromArrayType = (GenericArrayType) from; + return isAssignable(fromArrayType.getGenericComponentType(), to.getGenericComponentType()); + } else { + return false; + } + } + + private static boolean isAssignableFromGenericArrayType(GenericArrayType from, Type to) { + if (to instanceof Class) { + Class toClass = (Class) to; + if (!toClass.isArray()) { + return toClass == Object.class; // any T[] is assignable to Object + } + return isAssignable(from.getGenericComponentType(), toClass.getComponentType()); + } else if (to instanceof GenericArrayType) { + GenericArrayType toArrayType = (GenericArrayType) to; + return isAssignable(from.getGenericComponentType(), toArrayType.getGenericComponentType()); + } else { + return false; + } + } + + private static boolean matchTypeArgument(Type from, Type to) { + if (from.equals(to)) { + return true; + } + if (to instanceof WildcardType) { + return isAssignableToWildcardType(from, (WildcardType) to); + } + return false; + } + + private static Type supertypeBound(Type type) { + if (type instanceof WildcardType) { + return supertypeBound((WildcardType) type); + } + return type; + } + + private static Type supertypeBound(WildcardType type) { + Type[] upperBounds = type.getUpperBounds(); + if (upperBounds.length == 1) { + return supertypeBound(upperBounds[0]); + } else if (upperBounds.length == 0) { + return Object.class; + } else { + throw new AssertionError( + "There should be at most one upper bound for wildcard type: " + type); + } + } + + @Nullable private static Type subtypeBound(Type type) { + if (type instanceof WildcardType) { + return subtypeBound((WildcardType) type); + } else { + return type; + } + } + + @Nullable private static Type subtypeBound(WildcardType type) { + Type[] lowerBounds = type.getLowerBounds(); + if (lowerBounds.length == 1) { + return subtypeBound(lowerBounds[0]); + } else if (lowerBounds.length == 0) { + return null; + } else { + throw new AssertionError( + "Wildcard should have at most one lower bound: " + type); + } + } + + @VisibleForTesting static Class getRawType(Type type) { + // For wildcard or type variable, the first bound determines the runtime type. + return getRawTypes(type).iterator().next(); + } + + @VisibleForTesting static ImmutableSet> getRawTypes(Type type) { + if (type instanceof Class) { + return ImmutableSet.>of((Class) type); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + // JDK implementation declares getRawType() to return Class + return ImmutableSet.>of((Class) parameterizedType.getRawType()); + } else if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return ImmutableSet.>of(Types.getArrayClass( + getRawType(genericArrayType.getGenericComponentType()))); + } else if (type instanceof TypeVariable) { + return getRawTypes(((TypeVariable) type).getBounds()); + } else if (type instanceof WildcardType) { + return getRawTypes(((WildcardType) type).getUpperBounds()); + } else { + throw new AssertionError(type + " unsupported"); + } + } + + private static ImmutableSet> getRawTypes(Type[] types) { + ImmutableSet.Builder> builder = ImmutableSet.builder(); + for (Type type : types) { + builder.addAll(getRawTypes(type)); + } + return builder.build(); + } + + /** + * Returns the type token representing the generic type declaration of {@code cls}. For example: + * {@code TypeToken.getGenericType(Iterable.class)} returns {@code Iterable}. + * + *

    If {@code cls} isn't parameterized and isn't a generic array, the type token of the class is + * returned. + */ + @VisibleForTesting static TypeToken toGenericType(Class cls) { + if (cls.isArray()) { + Type arrayOfGenericType = Types.newArrayType( + // If we are passed with int[].class, don't turn it to GenericArrayType + toGenericType(cls.getComponentType()).runtimeType); + @SuppressWarnings("unchecked") // array is covariant + TypeToken result = (TypeToken) of(arrayOfGenericType); + return result; + } + TypeVariable>[] typeParams = cls.getTypeParameters(); + if (typeParams.length > 0) { + @SuppressWarnings("unchecked") // Like, it's Iterable for Iterable.class + TypeToken type = (TypeToken) + of(Types.newParameterizedType(cls, typeParams)); + return type; + } else { + return of(cls); + } + } + + private TypeToken getSupertypeFromUpperBounds( + Class supertype, Type[] upperBounds) { + for (Type upperBound : upperBounds) { + @SuppressWarnings("unchecked") // T's upperbound is . + TypeToken bound = (TypeToken) of(upperBound); + if (of(supertype).isAssignableFrom(bound)) { + @SuppressWarnings({"rawtypes", "unchecked"}) // guarded by the isAssignableFrom check. + TypeToken result = bound.getSupertype((Class) supertype); + return result; + } + } + throw new IllegalArgumentException(supertype + " isn't a super type of " + this); + } + + private TypeToken getSubtypeFromLowerBounds(Class subclass, Type[] lowerBounds) { + for (Type lowerBound : lowerBounds) { + @SuppressWarnings("unchecked") // T's lower bound is + TypeToken bound = (TypeToken) of(lowerBound); + // Java supports only one lowerbound anyway. + return bound.getSubtype(subclass); + } + throw new IllegalArgumentException(subclass + " isn't a subclass of " + this); + } + + private TypeToken getArraySupertype(Class supertype) { + // with component type, we have lost generic type information + // Use raw type so that compiler allows us to call getSupertype() + @SuppressWarnings("rawtypes") + TypeToken componentType = checkNotNull(getComponentType(), + "%s isn't a super type of %s", supertype, this); + // array is covariant. component type is super type, so is the array type. + @SuppressWarnings("unchecked") // going from raw type back to generics + TypeToken componentSupertype = componentType.getSupertype(supertype.getComponentType()); + @SuppressWarnings("unchecked") // component type is super type, so is array type. + TypeToken result = (TypeToken) + // If we are passed with int[].class, don't turn it to GenericArrayType + of(newArrayClassOrGenericArrayType(componentSupertype.runtimeType)); + return result; + } + + private TypeToken getArraySubtype(Class subclass) { + // array is covariant. component type is subtype, so is the array type. + TypeToken componentSubtype = getComponentType() + .getSubtype(subclass.getComponentType()); + @SuppressWarnings("unchecked") // component type is subtype, so is array type. + TypeToken result = (TypeToken) + // If we are passed with int[].class, don't turn it to GenericArrayType + of(newArrayClassOrGenericArrayType(componentSubtype.runtimeType)); + return result; + } + + private Type resolveTypeArgsForSubclass(Class subclass) { + if (runtimeType instanceof Class) { + // no resolution needed + return subclass; + } + // class Base {} + // class Sub extends Base {} + // Base.subtype(Sub.class): + + // Sub.getSupertype(Base.class) => Base + // => X=String, Y=Integer + // => Sub=Sub + TypeToken genericSubtype = toGenericType(subclass); + @SuppressWarnings({"rawtypes", "unchecked"}) // subclass isn't + Type supertypeWithArgsFromSubtype = genericSubtype + .getSupertype((Class) getRawType()) + .runtimeType; + return new TypeResolver().where(supertypeWithArgsFromSubtype, runtimeType) + .resolveType(genericSubtype.runtimeType); + } + + /** + * Creates an array class if {@code componentType} is a class, or else, a + * {@link GenericArrayType}. This is what Java7 does for generic array type + * parameters. + */ + private static Type newArrayClassOrGenericArrayType(Type componentType) { + return Types.JavaVersion.JAVA7.newArrayType(componentType); + } + + private static final class SimpleTypeToken extends TypeToken { + + SimpleTypeToken(Type type) { + super(type); + } + + private static final long serialVersionUID = 0; + } + + /** + * Collects parent types from a sub type. + * + * @param The type "kind". Either a TypeToken, or Class. + */ + private abstract static class TypeCollector { + + static final TypeCollector> FOR_GENERIC_TYPE = + new TypeCollector>() { + @Override Class getRawType(TypeToken type) { + return type.getRawType(); + } + + @Override Iterable> getInterfaces(TypeToken type) { + return type.getGenericInterfaces(); + } + + @Nullable + @Override TypeToken getSuperclass(TypeToken type) { + return type.getGenericSuperclass(); + } + }; + + static final TypeCollector> FOR_RAW_TYPE = + new TypeCollector>() { + @Override Class getRawType(Class type) { + return type; + } + + @Override Iterable> getInterfaces(Class type) { + return Arrays.asList(type.getInterfaces()); + } + + @Nullable + @Override Class getSuperclass(Class type) { + return type.getSuperclass(); + } + }; + + /** For just classes, we don't have to traverse interfaces. */ + final TypeCollector classesOnly() { + return new ForwardingTypeCollector(this) { + @Override Iterable getInterfaces(K type) { + return ImmutableSet.of(); + } + @Override ImmutableList collectTypes(Iterable types) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (K type : types) { + if (!getRawType(type).isInterface()) { + builder.add(type); + } + } + return super.collectTypes(builder.build()); + } + }; + } + + final ImmutableList collectTypes(K type) { + return collectTypes(ImmutableList.of(type)); + } + + ImmutableList collectTypes(Iterable types) { + // type -> order number. 1 for Object, 2 for anything directly below, so on so forth. + Map map = Maps.newHashMap(); + for (K type : types) { + collectTypes(type, map); + } + return sortKeysByValue(map, Ordering.natural().reverse()); + } + + /** Collects all types to map, and returns the total depth from T up to Object. */ + private int collectTypes(K type, Map map) { + Integer existing = map.get(this); + if (existing != null) { + // short circuit: if set contains type it already contains its supertypes + return existing; + } + int aboveMe = getRawType(type).isInterface() + ? 1 // interfaces should be listed before Object + : 0; + for (K interfaceType : getInterfaces(type)) { + aboveMe = Math.max(aboveMe, collectTypes(interfaceType, map)); + } + K superclass = getSuperclass(type); + if (superclass != null) { + aboveMe = Math.max(aboveMe, collectTypes(superclass, map)); + } + // TODO(benyu): should we include Object for interface? + // Also, CharSequence[] and Object[] for String[]? + map.put(type, aboveMe + 1); + return aboveMe + 1; + } + + private static ImmutableList sortKeysByValue( + final Map map, final Comparator valueComparator) { + Ordering keyOrdering = new Ordering() { + @Override public int compare(K left, K right) { + return valueComparator.compare(map.get(left), map.get(right)); + } + }; + return keyOrdering.immutableSortedCopy(map.keySet()); + } + + abstract Class getRawType(K type); + abstract Iterable getInterfaces(K type); + @Nullable abstract K getSuperclass(K type); + + private static class ForwardingTypeCollector extends TypeCollector { + + private final TypeCollector delegate; + + ForwardingTypeCollector(TypeCollector delegate) { + this.delegate = delegate; + } + + @Override Class getRawType(K type) { + return delegate.getRawType(type); + } + + @Override Iterable getInterfaces(K type) { + return delegate.getInterfaces(type); + } + + @Override K getSuperclass(K type) { + return delegate.getSuperclass(type); + } + } + } +} diff --git a/guava/src/com/google/common/reflect/Types.java b/guava/src/com/google/common/reflect/Types.java new file mode 100644 index 0000000..19264d3 --- /dev/null +++ b/guava/src/com/google/common/reflect/Types.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.transform; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Collection; + +import javax.annotation.Nullable; + +/** + * Utilities for working with {@link Type}. + * + * @author Ben Yu + */ +final class Types { + + /** Class#toString without the "class " and "interface " prefixes */ + private static final Function TYPE_TO_STRING = + new Function() { + @Override public String apply(Type from) { + return Types.toString(from); + } + }; + + private static final Joiner COMMA_JOINER = Joiner.on(", ").useForNull("null"); + + /** Returns the array type of {@code componentType}. */ + static Type newArrayType(Type componentType) { + if (componentType instanceof WildcardType) { + WildcardType wildcard = (WildcardType) componentType; + Type[] lowerBounds = wildcard.getLowerBounds(); + checkArgument(lowerBounds.length <= 1, "Wildcard cannot have more than one lower bounds."); + if (lowerBounds.length == 1) { + return supertypeOf(newArrayType(lowerBounds[0])); + } else { + Type[] upperBounds = wildcard.getUpperBounds(); + checkArgument(upperBounds.length == 1, "Wildcard should have only one upper bound."); + return subtypeOf(newArrayType(upperBounds[0])); + } + } + return JavaVersion.CURRENT.newArrayType(componentType); + } + + /** + * Returns a type where {@code rawType} is parameterized by + * {@code arguments} and is owned by {@code ownerType}. + */ + static ParameterizedType newParameterizedTypeWithOwner( + @Nullable Type ownerType, Class rawType, Type... arguments) { + if (ownerType == null) { + return newParameterizedType(rawType, arguments); + } + // ParameterizedTypeImpl constructor already checks, but we want to throw NPE before IAE + checkNotNull(arguments); + checkArgument(rawType.getEnclosingClass() != null, "Owner type for unenclosed %s", rawType); + return new ParameterizedTypeImpl(ownerType, rawType, arguments); + } + + /** + * Returns a type where {@code rawType} is parameterized by + * {@code arguments}. + */ + static ParameterizedType newParameterizedType(Class rawType, Type... arguments) { + return new ParameterizedTypeImpl( + ClassOwnership.JVM_BEHAVIOR.getOwnerType(rawType), rawType, arguments); + } + + /** Decides what owner type to use for constructing {@link ParameterizedType} from a raw class. */ + private enum ClassOwnership { + + OWNED_BY_ENCLOSING_CLASS { + @Nullable + @Override + Class getOwnerType(Class rawType) { + return rawType.getEnclosingClass(); + } + }, + LOCAL_CLASS_HAS_NO_OWNER { + @Nullable + @Override + Class getOwnerType(Class rawType) { + if (rawType.isLocalClass()) { + return null; + } else { + return rawType.getEnclosingClass(); + } + } + }; + + @Nullable abstract Class getOwnerType(Class rawType); + + static final ClassOwnership JVM_BEHAVIOR = detectJvmBehavior(); + + private static ClassOwnership detectJvmBehavior() { + class LocalClass {} + Class subclass = new LocalClass() {}.getClass(); + ParameterizedType parameterizedType = (ParameterizedType) + subclass.getGenericSuperclass(); + for (ClassOwnership behavior : ClassOwnership.values()) { + if (behavior.getOwnerType(LocalClass.class) == parameterizedType.getOwnerType()) { + return behavior; + } + } + throw new AssertionError(); + } + } + + /** + * Returns a new {@link TypeVariable} that belongs to {@code declaration} with + * {@code name} and {@code bounds}. + */ + static TypeVariable newTypeVariable( + D declaration, String name, Type... bounds) { + return new TypeVariableImpl( + declaration, + name, + (bounds.length == 0) + ? new Type[] { Object.class } + : bounds); + } + + /** Returns a new {@link WildcardType} with {@code upperBound}. */ + @VisibleForTesting static WildcardType subtypeOf(Type upperBound) { + return new WildcardTypeImpl(new Type[0], new Type[] { upperBound }); + } + + /** Returns a new {@link WildcardType} with {@code lowerBound}. */ + @VisibleForTesting static WildcardType supertypeOf(Type lowerBound) { + return new WildcardTypeImpl(new Type[] { lowerBound }, new Type[] { Object.class }); + } + + /** + * Returns human readable string representation of {@code type}. + *

      + *
    • For array type {@code Foo[]}, {@code "com.mypackage.Foo[]"} are + * returned. + *
    • For any class, {@code theClass.getName()} are returned. + *
    • For all other types, {@code type.toString()} are returned. + *
    + */ + static String toString(Type type) { + return (type instanceof Class) + ? ((Class) type).getName() + : type.toString(); + } + + @Nullable static Type getComponentType(Type type) { + checkNotNull(type); + if (type instanceof Class) { + return ((Class) type).getComponentType(); + } else if (type instanceof GenericArrayType) { + return ((GenericArrayType) type).getGenericComponentType(); + } else if (type instanceof WildcardType) { + return subtypeOfComponentType(((WildcardType) type).getUpperBounds()); + } else if (type instanceof TypeVariable) { + return subtypeOfComponentType(((TypeVariable) type).getBounds()); + } else { + return null; + } + } + + /** + * Returns {@code ? extends X} if any of {@code bounds} is a subtype of {@code X[]}; or null + * otherwise. + */ + @Nullable private static Type subtypeOfComponentType(Type[] bounds) { + for (Type bound : bounds) { + Type componentType = getComponentType(bound); + if (componentType != null) { + // Only the first bound can be a class or array. + // Bounds after the first can only be interfaces. + if (componentType instanceof Class) { + Class componentClass = (Class) componentType; + if (componentClass.isPrimitive()) { + return componentClass; + } + } + return subtypeOf(componentType); + } + } + return null; + } + + static boolean containsTypeVariable(@Nullable Type type) { + if (type instanceof TypeVariable) { + return true; + } + if (type instanceof GenericArrayType) { + return containsTypeVariable(((GenericArrayType) type).getGenericComponentType()); + } + if (type instanceof ParameterizedType) { + return containsTypeVariable(((ParameterizedType) type).getActualTypeArguments()); + } + if (type instanceof WildcardType) { + WildcardType wildcard = (WildcardType) type; + return containsTypeVariable(wildcard.getUpperBounds()) + || containsTypeVariable(wildcard.getLowerBounds()); + } + return false; + } + + private static boolean containsTypeVariable(Type[] types) { + for (Type paramType : types) { + if (containsTypeVariable(paramType)) { + return true; + } + } + return false; + } + + private static final class GenericArrayTypeImpl + implements GenericArrayType, Serializable { + + private final Type componentType; + + GenericArrayTypeImpl(Type componentType) { + this.componentType = JavaVersion.CURRENT.usedInGenericType(componentType); + } + + @Override public Type getGenericComponentType() { + return componentType; + } + + @Override public String toString() { + return Types.toString(componentType) + "[]"; + } + + @Override public int hashCode() { + return componentType.hashCode(); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof GenericArrayType) { + GenericArrayType that = (GenericArrayType) obj; + return Objects.equal( + getGenericComponentType(), that.getGenericComponentType()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + private static final class ParameterizedTypeImpl + implements ParameterizedType, Serializable { + + private final Type ownerType; + private final ImmutableList argumentsList; + private final Class rawType; + + ParameterizedTypeImpl( + @Nullable Type ownerType, Class rawType, Type[] typeArguments) { + checkNotNull(rawType); + checkArgument(typeArguments.length == rawType.getTypeParameters().length); + disallowPrimitiveType(typeArguments, "type parameter"); + this.ownerType = ownerType; + this.rawType = rawType; + this.argumentsList = JavaVersion.CURRENT.usedInGenericType(typeArguments); + } + + @Override public Type[] getActualTypeArguments() { + return toArray(argumentsList); + } + + @Override public Type getRawType() { + return rawType; + } + + @Override public Type getOwnerType() { + return ownerType; + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(); + if (ownerType != null) { + builder.append(Types.toString(ownerType)).append('.'); + } + builder.append(rawType.getName()) + .append('<') + .append(COMMA_JOINER.join(transform(argumentsList, TYPE_TO_STRING))) + .append('>'); + return builder.toString(); + } + + @Override public int hashCode() { + return (ownerType == null ? 0 : ownerType.hashCode()) + ^ argumentsList.hashCode() ^ rawType.hashCode(); + } + + @Override public boolean equals(Object other) { + if (!(other instanceof ParameterizedType)) { + return false; + } + ParameterizedType that = (ParameterizedType) other; + return getRawType().equals(that.getRawType()) + && Objects.equal(getOwnerType(), that.getOwnerType()) + && Arrays.equals( + getActualTypeArguments(), that.getActualTypeArguments()); + } + + private static final long serialVersionUID = 0; + } + + private static final class TypeVariableImpl + implements TypeVariable { + + private final D genericDeclaration; + private final String name; + private final ImmutableList bounds; + + TypeVariableImpl(D genericDeclaration, String name, Type[] bounds) { + disallowPrimitiveType(bounds, "bound for type variable"); + this.genericDeclaration = checkNotNull(genericDeclaration); + this.name = checkNotNull(name); + this.bounds = ImmutableList.copyOf(bounds); + } + + @Override public Type[] getBounds() { + return toArray(bounds); + } + + @Override public D getGenericDeclaration() { + return genericDeclaration; + } + + @Override public String getName() { + return name; + } + + @Override public String toString() { + return name; + } + + @Override public int hashCode() { + return genericDeclaration.hashCode() ^ name.hashCode(); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof TypeVariable) { + TypeVariable that = (TypeVariable) obj; + return name.equals(that.getName()) + && genericDeclaration.equals(that.getGenericDeclaration()); + } + return false; + } + } + + static final class WildcardTypeImpl implements WildcardType, Serializable { + + private final ImmutableList lowerBounds; + private final ImmutableList upperBounds; + + WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds) { + disallowPrimitiveType(lowerBounds, "lower bound for wildcard"); + disallowPrimitiveType(upperBounds, "upper bound for wildcard"); + this.lowerBounds = JavaVersion.CURRENT.usedInGenericType(lowerBounds); + this.upperBounds = JavaVersion.CURRENT.usedInGenericType(upperBounds); + } + + @Override public Type[] getLowerBounds() { + return toArray(lowerBounds); + } + + @Override public Type[] getUpperBounds() { + return toArray(upperBounds); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof WildcardType) { + WildcardType that = (WildcardType) obj; + return lowerBounds.equals(Arrays.asList(that.getLowerBounds())) + && upperBounds.equals(Arrays.asList(that.getUpperBounds())); + } + return false; + } + + @Override public int hashCode() { + return lowerBounds.hashCode() ^ upperBounds.hashCode(); + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder("?"); + for (Type lowerBound : lowerBounds) { + builder.append(" super ").append(Types.toString(lowerBound)); + } + for (Type upperBound : filterUpperBounds(upperBounds)) { + builder.append(" extends ").append(Types.toString(upperBound)); + } + return builder.toString(); + } + + private static final long serialVersionUID = 0; + } + + private static Type[] toArray(Collection types) { + return types.toArray(new Type[types.size()]); + } + + private static Iterable filterUpperBounds(Iterable bounds) { + return Iterables.filter( + bounds, Predicates.not(Predicates.equalTo(Object.class))); + } + + private static void disallowPrimitiveType(Type[] types, String usedAs) { + for (Type type : types) { + if (type instanceof Class) { + Class cls = (Class) type; + checkArgument(!cls.isPrimitive(), + "Primitive type '%s' used as %s", cls, usedAs); + } + } + } + + static IllegalArgumentException buildUnexpectedTypeException( + Type type, Class... expected) { + // Build exception message + StringBuilder exceptionMessage = + new StringBuilder("Unexpected type. Expected one of: "); + for (Class clazz : expected) { + exceptionMessage.append(clazz.getName()).append(", "); + } + exceptionMessage.append("but got: ").append(type.getClass().getName()) + .append(", for type: ").append(toString(type)).append('.'); + + return new IllegalArgumentException(exceptionMessage.toString()); + } + + /** Returns the {@code Class} object of arrays with {@code componentType}. */ + static Class getArrayClass(Class componentType) { + // TODO(user): This is not the most efficient way to handle generic + // arrays, but is there another way to extract the array class in a + // non-hacky way (i.e. using String value class names- "[L...")? + return Array.newInstance(componentType, 0).getClass(); + } + + // TODO(benyu): Once we are on Java 7, delete this abstraction + enum JavaVersion { + + JAVA6 { + @Override GenericArrayType newArrayType(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + @Override Type usedInGenericType(Type type) { + checkNotNull(type); + if (type instanceof Class) { + Class cls = (Class) type; + if (cls.isArray()) { + return new GenericArrayTypeImpl(cls.getComponentType()); + } + } + return type; + } + }, + JAVA7 { + @Override Type newArrayType(Type componentType) { + if (componentType instanceof Class) { + return getArrayClass((Class) componentType); + } else { + return new GenericArrayTypeImpl(componentType); + } + } + @Override Type usedInGenericType(Type type) { + return checkNotNull(type); + } + } + ; + + static final JavaVersion CURRENT = + (new TypeCapture() {}.capture() instanceof Class) + ? JAVA7 : JAVA6; + abstract Type newArrayType(Type componentType); + abstract Type usedInGenericType(Type type); + + final ImmutableList usedInGenericType(Type[] types) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (Type type : types) { + builder.add(usedInGenericType(type)); + } + return builder.build(); + } + } + + private Types() {} +} diff --git a/guava/src/com/google/common/reflect/package-info.java b/guava/src/com/google/common/reflect/package-info.java new file mode 100644 index 0000000..e8ac02a --- /dev/null +++ b/guava/src/com/google/common/reflect/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains utilities to work with Java reflection. + * It is a part of the open-source + * Guava libraries. + */ +@javax.annotation.ParametersAreNonnullByDefault +package com.google.common.reflect; diff --git a/guava/src/com/google/common/util/concurrent/AbstractCheckedFuture.java b/guava/src/com/google/common/util/concurrent/AbstractCheckedFuture.java new file mode 100644 index 0000000..dfaf343 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractCheckedFuture.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A delegating wrapper around a {@link ListenableFuture} that adds support for + * the {@link #checkedGet()} and {@link #checkedGet(long, TimeUnit)} methods. + * + * @author Sven Mawson + * @since 1.0 + */ +@Beta +public abstract class AbstractCheckedFuture + extends ForwardingListenableFuture.SimpleForwardingListenableFuture + implements CheckedFuture { + /** + * Constructs an {@code AbstractCheckedFuture} that wraps a delegate. + */ + protected AbstractCheckedFuture(ListenableFuture delegate) { + super(delegate); + } + + /** + * Translates from an {@link InterruptedException}, + * {@link CancellationException} or {@link ExecutionException} thrown by + * {@code get} to an exception of type {@code X} to be thrown by + * {@code checkedGet}. Subclasses must implement this method. + * + *

    If {@code e} is an {@code InterruptedException}, the calling + * {@code checkedGet} method has already restored the interrupt after catching + * the exception. If an implementation of {@link #mapException(Exception)} + * wishes to swallow the interrupt, it can do so by calling + * {@link Thread#interrupted()}. + * + *

    Subclasses may choose to throw, rather than return, a subclass of + * {@code RuntimeException} to allow creating a CheckedFuture that throws + * both checked and unchecked exceptions. + */ + protected abstract X mapException(Exception e); + + /** + * {@inheritDoc} + * + *

    This implementation calls {@link #get()} and maps that method's standard + * exceptions to instances of type {@code X} using {@link #mapException}. + * + *

    In addition, if {@code get} throws an {@link InterruptedException}, this + * implementation will set the current thread's interrupt status before + * calling {@code mapException}. + * + * @throws X if {@link #get()} throws an {@link InterruptedException}, + * {@link CancellationException}, or {@link ExecutionException} + */ + @Override + public V checkedGet() throws X { + try { + return get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw mapException(e); + } catch (CancellationException e) { + throw mapException(e); + } catch (ExecutionException e) { + throw mapException(e); + } + } + + /** + * {@inheritDoc} + * + *

    This implementation calls {@link #get(long, TimeUnit)} and maps that + * method's standard exceptions (excluding {@link TimeoutException}, which is + * propagated) to instances of type {@code X} using {@link #mapException}. + * + *

    In addition, if {@code get} throws an {@link InterruptedException}, this + * implementation will set the current thread's interrupt status before + * calling {@code mapException}. + * + * @throws X if {@link #get()} throws an {@link InterruptedException}, + * {@link CancellationException}, or {@link ExecutionException} + * @throws TimeoutException {@inheritDoc} + */ + @Override + public V checkedGet(long timeout, TimeUnit unit) throws TimeoutException, X { + try { + return get(timeout, unit); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw mapException(e); + } catch (CancellationException e) { + throw mapException(e); + } catch (ExecutionException e) { + throw mapException(e); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java b/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java new file mode 100644 index 0000000..6926c85 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.base.Throwables; + +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base class for services that can implement {@link #startUp}, {@link #run} and + * {@link #shutDown} methods. This class uses a single thread to execute the + * service; consider {@link AbstractService} if you would like to manage any + * threading manually. + * + * @author Jesse Wilson + * @since 1.0 + */ +@Beta +public abstract class AbstractExecutionThreadService implements Service { + private static final Logger logger = Logger.getLogger( + AbstractExecutionThreadService.class.getName()); + + /* use AbstractService for state management */ + private final Service delegate = new AbstractService() { + @Override protected final void doStart() { + executor().execute(new Runnable() { + @Override + public void run() { + try { + startUp(); + notifyStarted(); + + if (isRunning()) { + try { + AbstractExecutionThreadService.this.run(); + } catch (Throwable t) { + try { + shutDown(); + } catch (Exception ignored) { + logger.log(Level.WARNING, + "Error while attempting to shut down the service" + + " after failure.", ignored); + } + throw t; + } + } + + shutDown(); + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + throw Throwables.propagate(t); + } + } + }); + } + + @Override protected void doStop() { + triggerShutdown(); + } + }; + + /** + * Constructor for use by subclasses. + */ + protected AbstractExecutionThreadService() {} + + /** + * Start the service. This method is invoked on the execution thread. + * + *

    By default this method does nothing. + */ + protected void startUp() throws Exception {} + + /** + * Run the service. This method is invoked on the execution thread. + * Implementations must respond to stop requests. You could poll for lifecycle + * changes in a work loop: + *

    +   *   public void run() {
    +   *     while ({@link #isRunning()}) {
    +   *       // perform a unit of work
    +   *     }
    +   *   }
    +   * 
    + * ...or you could respond to stop requests by implementing {@link + * #triggerShutdown()}, which should cause {@link #run()} to return. + */ + protected abstract void run() throws Exception; + + /** + * Stop the service. This method is invoked on the execution thread. + * + *

    By default this method does nothing. + */ + // TODO: consider supporting a TearDownTestCase-like API + protected void shutDown() throws Exception {} + + /** + * Invoked to request the service to stop. + * + *

    By default this method does nothing. + */ + protected void triggerShutdown() {} + + /** + * Returns the {@link Executor} that will be used to run this service. + * Subclasses may override this method to use a custom {@link Executor}, which + * may configure its worker thread with a specific name, thread group or + * priority. The returned executor's {@link Executor#execute(Runnable) + * execute()} method is called when this service is started, and should return + * promptly. + * + *

    The default implementation returns a new {@link Executor} that sets the + * name of its threads to the string returned by {@link #getServiceName} + */ + protected Executor executor() { + return new Executor() { + @Override + public void execute(Runnable command) { + new Thread(command, getServiceName()).start(); + } + }; + } + + @Override public String toString() { + return getServiceName() + " [" + state() + "]"; + } + + // We override instead of using ForwardingService so that these can be final. + + @Override public final ListenableFuture start() { + return delegate.start(); + } + + @Override public final State startAndWait() { + return delegate.startAndWait(); + } + + @Override public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override public final State state() { + return delegate.state(); + } + + @Override public final ListenableFuture stop() { + return delegate.stop(); + } + + @Override public final State stopAndWait() { + return delegate.stopAndWait(); + } + + @Override public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + /** + * Returns the name of this service. {@link AbstractExecutionThreadService} + * may include the name in debugging output. + * + *

    Subclasses may override this method. + * + * @since 10.0 + */ + protected String getServiceName() { + return getClass().getSimpleName(); + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractFuture.java b/guava/src/com/google/common/util/concurrent/AbstractFuture.java new file mode 100644 index 0000000..28b2cc0 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractFuture.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import javax.annotation.Nullable; + +/** + * An abstract implementation of the {@link ListenableFuture} interface. This + * class is preferable to {@link java.util.concurrent.FutureTask} for two + * reasons: It implements {@code ListenableFuture}, and it does not implement + * {@code Runnable}. (If you want a {@code Runnable} implementation of {@code + * ListenableFuture}, create a {@link ListenableFutureTask}, or submit your + * tasks to a {@link ListeningExecutorService}.) + * + *

    This class implements all methods in {@code ListenableFuture}. + * Subclasses should provide a way to set the result of the computation through + * the protected methods {@link #set(Object)} and + * {@link #setException(Throwable)}. Subclasses may also override {@link + * #interruptTask()}, which will be invoked automatically if a call to {@link + * #cancel(boolean) cancel(true)} succeeds in canceling the future. + * + *

    {@code AbstractFuture} uses an {@link AbstractQueuedSynchronizer} to deal + * with concurrency issues and guarantee thread safety. + * + *

    The state changing methods all return a boolean indicating success or + * failure in changing the future's state. Valid states are running, + * completed, failed, or cancelled. + * + *

    This class uses an {@link ExecutionList} to guarantee that all registered + * listeners will be executed, either when the future finishes or, for listeners + * that are added after the future completes, immediately. + * {@code Runnable}-{@code Executor} pairs are stored in the execution list but + * are not necessarily executed in the order in which they were added. (If a + * listener is added after the Future is complete, it will be executed + * immediately, even if earlier listeners have not been executed. Additionally, + * executors need not guarantee FIFO execution, or different listeners may run + * in different executors.) + * + * @author Sven Mawson + * @since 1.0 + */ +public abstract class AbstractFuture implements ListenableFuture { + + /** Synchronization control for AbstractFutures. */ + private final Sync sync = new Sync(); + + // The execution list to hold our executors. + private final ExecutionList executionList = new ExecutionList(); + + /** + * Constructor for use by subclasses. + */ + protected AbstractFuture() {} + + /* + * Improve the documentation of when InterruptedException is thrown. Our + * behavior matches the JDK's, but the JDK's documentation is misleading. + */ + /** + * {@inheritDoc} + * + *

    The default {@link AbstractFuture} implementation throws {@code + * InterruptedException} if the current thread is interrupted before or during + * the call, even if the value is already available. + * + * @throws InterruptedException if the current thread was interrupted before + * or during the call (optional but recommended). + * @throws CancellationException {@inheritDoc} + */ + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, + TimeoutException, ExecutionException { + return sync.get(unit.toNanos(timeout)); + } + + /* + * Improve the documentation of when InterruptedException is thrown. Our + * behavior matches the JDK's, but the JDK's documentation is misleading. + */ + /** + * {@inheritDoc} + * + *

    The default {@link AbstractFuture} implementation throws {@code + * InterruptedException} if the current thread is interrupted before or during + * the call, even if the value is already available. + * + * @throws InterruptedException if the current thread was interrupted before + * or during the call (optional but recommended). + * @throws CancellationException {@inheritDoc} + */ + @Override + public V get() throws InterruptedException, ExecutionException { + return sync.get(); + } + + @Override + public boolean isDone() { + return sync.isDone(); + } + + @Override + public boolean isCancelled() { + return sync.isCancelled(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (!sync.cancel()) { + return false; + } + executionList.execute(); + if (mayInterruptIfRunning) { + interruptTask(); + } + return true; + } + + /** + * Subclasses can override this method to implement interruption of the + * future's computation. The method is invoked automatically by a successful + * call to {@link #cancel(boolean) cancel(true)}. + * + *

    The default implementation does nothing. + * + * @since 10.0 + */ + protected void interruptTask() { + } + + /** + * {@inheritDoc} + * + * @since 10.0 + */ + @Override + public void addListener(Runnable listener, Executor exec) { + executionList.add(listener, exec); + } + + /** + * Subclasses should invoke this method to set the result of the computation + * to {@code value}. This will set the state of the future to + * {@link AbstractFuture.Sync#COMPLETED} and invoke the listeners if the + * state was successfully changed. + * + * @param value the value that was the result of the task. + * @return true if the state was successfully changed. + */ + protected boolean set(@Nullable V value) { + boolean result = sync.set(value); + if (result) { + executionList.execute(); + } + return result; + } + + /** + * Subclasses should invoke this method to set the result of the computation + * to an error, {@code throwable}. This will set the state of the future to + * {@link AbstractFuture.Sync#COMPLETED} and invoke the listeners if the + * state was successfully changed. + * + * @param throwable the exception that the task failed with. + * @return true if the state was successfully changed. + * @throws Error if the throwable was an {@link Error}. + */ + protected boolean setException(Throwable throwable) { + boolean result = sync.setException(checkNotNull(throwable)); + if (result) { + executionList.execute(); + } + + // If it's an Error, we want to make sure it reaches the top of the + // call stack, so we rethrow it. + if (throwable instanceof Error) { + throw (Error) throwable; + } + return result; + } + + /** + *

    Following the contract of {@link AbstractQueuedSynchronizer} we create a + * private subclass to hold the synchronizer. This synchronizer is used to + * implement the blocking and waiting calls as well as to handle state changes + * in a thread-safe manner. The current state of the future is held in the + * Sync state, and the lock is released whenever the state changes to either + * {@link #COMPLETED} or {@link #CANCELLED}. + * + *

    To avoid races between threads doing release and acquire, we transition + * to the final state in two steps. One thread will successfully CAS from + * RUNNING to COMPLETING, that thread will then set the result of the + * computation, and only then transition to COMPLETED or CANCELLED. + * + *

    We don't use the integer argument passed between acquire methods so we + * pass around a -1 everywhere. + */ + static final class Sync extends AbstractQueuedSynchronizer { + + private static final long serialVersionUID = 0L; + + /* Valid states. */ + static final int RUNNING = 0; + static final int COMPLETING = 1; + static final int COMPLETED = 2; + static final int CANCELLED = 4; + + private V value; + private Throwable exception; + + /* + * Acquisition succeeds if the future is done, otherwise it fails. + */ + @Override + protected int tryAcquireShared(int ignored) { + if (isDone()) { + return 1; + } + return -1; + } + + /* + * We always allow a release to go through, this means the state has been + * successfully changed and the result is available. + */ + @Override + protected boolean tryReleaseShared(int finalState) { + setState(finalState); + return true; + } + + /** + * Blocks until the task is complete or the timeout expires. Throws a + * {@link TimeoutException} if the timer expires, otherwise behaves like + * {@link #get()}. + */ + V get(long nanos) throws TimeoutException, CancellationException, + ExecutionException, InterruptedException { + + // Attempt to acquire the shared lock with a timeout. + if (!tryAcquireSharedNanos(-1, nanos)) { + throw new TimeoutException("Timeout waiting for task."); + } + + return getValue(); + } + + /** + * Blocks until {@link #complete(Object, Throwable, int)} has been + * successfully called. Throws a {@link CancellationException} if the task + * was cancelled, or a {@link ExecutionException} if the task completed with + * an error. + */ + V get() throws CancellationException, ExecutionException, + InterruptedException { + + // Acquire the shared lock allowing interruption. + acquireSharedInterruptibly(-1); + return getValue(); + } + + /** + * Implementation of the actual value retrieval. Will return the value + * on success, an exception on failure, a cancellation on cancellation, or + * an illegal state if the synchronizer is in an invalid state. + */ + private V getValue() throws CancellationException, ExecutionException { + int state = getState(); + switch (state) { + case COMPLETED: + if (exception != null) { + throw new ExecutionException(exception); + } else { + return value; + } + + case CANCELLED: + throw new CancellationException("Task was cancelled."); + + default: + throw new IllegalStateException( + "Error, synchronizer in invalid state: " + state); + } + } + + /** + * Checks if the state is {@link #COMPLETED} or {@link #CANCELLED}. + */ + boolean isDone() { + return (getState() & (COMPLETED | CANCELLED)) != 0; + } + + /** + * Checks if the state is {@link #CANCELLED}. + */ + boolean isCancelled() { + return getState() == CANCELLED; + } + + /** + * Transition to the COMPLETED state and set the value. + */ + boolean set(@Nullable V v) { + return complete(v, null, COMPLETED); + } + + /** + * Transition to the COMPLETED state and set the exception. + */ + boolean setException(Throwable t) { + return complete(null, t, COMPLETED); + } + + /** + * Transition to the CANCELLED state. + */ + boolean cancel() { + return complete(null, null, CANCELLED); + } + + /** + * Implementation of completing a task. Either {@code v} or {@code t} will + * be set but not both. The {@code finalState} is the state to change to + * from {@link #RUNNING}. If the state is not in the RUNNING state we + * return {@code false} after waiting for the state to be set to a valid + * final state ({@link #COMPLETED} or {@link #CANCELLED}). + * + * @param v the value to set as the result of the computation. + * @param t the exception to set as the result of the computation. + * @param finalState the state to transition to. + */ + private boolean complete(@Nullable V v, @Nullable Throwable t, + int finalState) { + boolean doCompletion = compareAndSetState(RUNNING, COMPLETING); + if (doCompletion) { + // If this thread successfully transitioned to COMPLETING, set the value + // and exception and then release to the final state. + this.value = v; + this.exception = t; + releaseShared(finalState); + } else if (getState() == COMPLETING) { + // If some other thread is currently completing the future, block until + // they are done so we can guarantee completion. + acquireShared(-1); + } + return doCompletion; + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractIdleService.java b/guava/src/com/google/common/util/concurrent/AbstractIdleService.java new file mode 100644 index 0000000..6d49ddd --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractIdleService.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.base.Throwables; + +import java.util.concurrent.Executor; + +/** + * Base class for services that do not need a thread while "running" + * but may need one during startup and shutdown. Subclasses can + * implement {@link #startUp} and {@link #shutDown} methods, each + * which run in a executor which by default uses a separate thread + * for each method. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public abstract class AbstractIdleService implements Service { + + /* use AbstractService for state management */ + private final Service delegate = new AbstractService() { + @Override protected final void doStart() { + executor(State.STARTING).execute(new Runnable() { + @Override public void run() { + try { + startUp(); + notifyStarted(); + } catch (Throwable t) { + notifyFailed(t); + throw Throwables.propagate(t); + } + } + }); + } + + @Override protected final void doStop() { + executor(State.STOPPING).execute(new Runnable() { + @Override public void run() { + try { + shutDown(); + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + throw Throwables.propagate(t); + } + } + }); + } + }; + + /** Start the service. */ + protected abstract void startUp() throws Exception; + + /** Stop the service. */ + protected abstract void shutDown() throws Exception; + + /** + * Returns the {@link Executor} that will be used to run this service. + * Subclasses may override this method to use a custom {@link Executor}, which + * may configure its worker thread with a specific name, thread group or + * priority. The returned executor's {@link Executor#execute(Runnable) + * execute()} method is called when this service is started and stopped, + * and should return promptly. + * + * @param state {@link Service.State#STARTING} or + * {@link Service.State#STOPPING}, used by the default implementation for + * naming the thread + */ + protected Executor executor(final State state) { + return new Executor() { + @Override + public void execute(Runnable command) { + new Thread(command, getServiceName() + " " + state).start(); + } + }; + } + + @Override public String toString() { + return getServiceName() + " [" + state() + "]"; + } + + // We override instead of using ForwardingService so that these can be final. + + @Override public final ListenableFuture start() { + return delegate.start(); + } + + @Override public final State startAndWait() { + return delegate.startAndWait(); + } + + @Override public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override public final State state() { + return delegate.state(); + } + + @Override public final ListenableFuture stop() { + return delegate.stop(); + } + + @Override public final State stopAndWait() { + return delegate.stopAndWait(); + } + + @Override public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + private String getServiceName() { + return getClass().getSimpleName(); + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java b/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java new file mode 100644 index 0000000..4e867a6 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java @@ -0,0 +1,163 @@ +/* + * This file is a modified version of + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/AbstractExecutorService.java?revision=1.35 + * which contained the following notice: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 Expert Group and released to the + * public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/ + * + * Rationale for copying: + * Guava targets JDK5, whose AbstractExecutorService class lacks the newTaskFor protected + * customization methods needed by MoreExecutors.listeningDecorator. This class is a copy of + * AbstractExecutorService from the JSR166 CVS repository. It contains the desired methods. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.MoreExecutors.invokeAnyImpl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Implements {@link ListeningExecutorService} execution methods atop the abstract {@link #execute} + * method. More concretely, the {@code submit}, {@code invokeAny} and {@code invokeAll} methods + * create {@link ListenableFutureTask} instances and pass them to {@link #execute}. + * + *

    In addition to {@link #execute}, subclasses must implement all methods related to shutdown and + * termination. + * + * @author Doug Lea + */ +abstract class AbstractListeningExecutorService implements ListeningExecutorService { + @Override public ListenableFuture submit(Runnable task) { + ListenableFutureTask ftask = ListenableFutureTask.create(task, null); + execute(ftask); + return ftask; + } + + @Override public ListenableFuture submit(Runnable task, T result) { + ListenableFutureTask ftask = ListenableFutureTask.create(task, result); + execute(ftask); + return ftask; + } + + @Override public ListenableFuture submit(Callable task) { + ListenableFutureTask ftask = ListenableFutureTask.create(task); + execute(ftask); + return ftask; + } + + @Override public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + try { + return invokeAnyImpl(this, tasks, false, 0); + } catch (TimeoutException cannotHappen) { + throw new AssertionError(); + } + } + + @Override public T invokeAny( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return invokeAnyImpl(this, tasks, true, unit.toNanos(timeout)); + } + + @Override public List> invokeAll(Collection> tasks) + throws InterruptedException { + if (tasks == null) { + throw new NullPointerException(); + } + List> futures = new ArrayList>(tasks.size()); + boolean done = false; + try { + for (Callable t : tasks) { + ListenableFutureTask f = ListenableFutureTask.create(t); + futures.add(f); + execute(f); + } + for (Future f : futures) { + if (!f.isDone()) { + try { + f.get(); + } catch (CancellationException ignore) { + } catch (ExecutionException ignore) { + } + } + } + done = true; + return futures; + } finally { + if (!done) { + for (Future f : futures) { + f.cancel(true); + } + } + } + } + + @Override public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + if (tasks == null || unit == null) { + throw new NullPointerException(); + } + long nanos = unit.toNanos(timeout); + List> futures = new ArrayList>(tasks.size()); + boolean done = false; + try { + for (Callable t : tasks) { + futures.add(ListenableFutureTask.create(t)); + } + + long lastTime = System.nanoTime(); + + // Interleave time checks and calls to execute in case + // executor doesn't have any/much parallelism. + Iterator> it = futures.iterator(); + while (it.hasNext()) { + execute((Runnable) (it.next())); + long now = System.nanoTime(); + nanos -= now - lastTime; + lastTime = now; + if (nanos <= 0) { + return futures; + } + } + + for (Future f : futures) { + if (!f.isDone()) { + if (nanos <= 0) { + return futures; + } + try { + f.get(nanos, TimeUnit.NANOSECONDS); + } catch (CancellationException ignore) { + } catch (ExecutionException ignore) { + } catch (TimeoutException toe) { + return futures; + } + long now = System.nanoTime(); + nanos -= now - lastTime; + lastTime = now; + } + } + done = true; + return futures; + } finally { + if (!done) { + for (Future f : futures) { + f.cancel(true); + } + } + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java b/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java new file mode 100644 index 0000000..cfc7475 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.concurrent.GuardedBy; + +/** + * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in + * the "running" state need to perform a periodic task. Subclasses can implement {@link #startUp}, + * {@link #shutDown} and also a {@link #runOneIteration} method that will be executed periodically. + * + *

    This class uses the {@link ScheduledExecutorService} returned from {@link #executor} to run + * the {@link #startUp} and {@link #shutDown} methods and also uses that service to schedule the + * {@link #runOneIteration} that will be executed periodically as specified by its + * {@link Scheduler}. When this service is asked to stop via {@link #stop} or {@link #stopAndWait}, + * it will cancel the periodic task (but not interrupt it) and wait for it to stop before running + * the {@link #shutDown} method. + * + *

    Subclasses are guaranteed that the life cycle methods ({@link #runOneIteration}, {@link + * #startUp} and {@link #shutDown}) will never run concurrently. Notably, if any execution of {@link + * #runOneIteration} takes longer than its schedule defines, then subsequent executions may start + * late. Also, all life cycle methods are executed with a lock held, so subclasses can safely + * modify shared state without additional synchronization necessary for visibility to later + * executions of the life cycle methods. + * + *

    Usage Example

    + * + * Here is a sketch of a service which crawls a website and uses the scheduling capabilities to + * rate limit itself.
     {@code
    + * class CrawlingService extends AbstractScheduledService {
    + *   private Set visited;
    + *   private Queue toCrawl;
    + *   protected void startUp() throws Exception {
    + *     toCrawl = readStartingUris();
    + *   }
    + *
    + *   protected void runOneIteration() throws Exception {
    + *     Uri uri = toCrawl.remove();
    + *     Collection newUris = crawl(uri);
    + *     visited.add(uri);
    + *     for (Uri newUri : newUris) {
    + *       if (!visited.contains(newUri)) { toCrawl.add(newUri); }
    + *     }
    + *   }
    + *
    + *   protected void shutDown() throws Exception {
    + *     saveUris(toCrawl);
    + *   }
    + *
    + *   protected Scheduler scheduler() {
    + *     return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS);
    + *   }
    + * }}
    + * + * This class uses the life cycle methods to read in a list of starting URIs and save the set of + * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to + * rate limit the number of queries we perform. + * + * @author Luke Sandberg + * @since 11.0 + */ +@Beta +public abstract class AbstractScheduledService implements Service { + private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); + + /** + * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its + * task. + * + *

    Consider using the {@link #newFixedDelaySchedule} and {@link #newFixedRateSchedule} factory + * methods, these provide {@link Scheduler} instances for the common use case of running the + * service with a fixed schedule. If more flexibility is needed then consider subclassing the + * {@link CustomScheduler} abstract class in preference to creating your own {@link Scheduler} + * implementation. + * + * @author Luke Sandberg + * @since 11.0 + */ + public abstract static class Scheduler { + /** + * Returns a {@link Scheduler} that schedules the task using the + * {@link ScheduledExecutorService#scheduleWithFixedDelay} method. + * + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one execution and the commencement of the + * next + * @param unit the time unit of the initialDelay and delay parameters + */ + public static Scheduler newFixedDelaySchedule(final long initialDelay, final long delay, + final TimeUnit unit) { + return new Scheduler() { + @Override + public Future schedule(AbstractService service, ScheduledExecutorService executor, + Runnable task) { + return executor.scheduleWithFixedDelay(task, initialDelay, delay, unit); + } + }; + } + + /** + * Returns a {@link Scheduler} that schedules the task using the + * {@link ScheduledExecutorService#scheduleAtFixedRate} method. + * + * @param initialDelay the time to delay first execution + * @param period the period between successive executions of the task + * @param unit the time unit of the initialDelay and period parameters + */ + public static Scheduler newFixedRateSchedule(final long initialDelay, final long period, + final TimeUnit unit) { + return new Scheduler() { + @Override + public Future schedule(AbstractService service, ScheduledExecutorService executor, + Runnable task) { + return executor.scheduleAtFixedRate(task, initialDelay, period, unit); + } + }; + } + + /** Schedules the task to run on the provided executor on behalf of the service. */ + abstract Future schedule(AbstractService service, ScheduledExecutorService executor, + Runnable runnable); + + private Scheduler() {} + } + + /* use AbstractService for state management */ + private final AbstractService delegate = new AbstractService() { + + // A handle to the running task so that we can stop it when a shutdown has been requested. + // These two fields are volatile because their values will be accessed from multiple threads. + private volatile Future runningTask; + private volatile ScheduledExecutorService executorService; + + // This lock protects the task so we can ensure that none of the template methods (startUp, + // shutDown or runOneIteration) run concurrently with one another. + private final ReentrantLock lock = new ReentrantLock(); + + private final Runnable task = new Runnable() { + @Override public void run() { + lock.lock(); + try { + AbstractScheduledService.this.runOneIteration(); + } catch (Throwable t) { + try { + shutDown(); + } catch (Exception ignored) { + logger.log(Level.WARNING, + "Error while attempting to shut down the service after failure.", ignored); + } + notifyFailed(t); + throw Throwables.propagate(t); + } finally { + lock.unlock(); + } + } + }; + + @Override protected final void doStart() { + executorService = executor(); + executorService.execute(new Runnable() { + @Override public void run() { + lock.lock(); + try { + startUp(); + runningTask = scheduler().schedule(delegate, executorService, task); + notifyStarted(); + } catch (Throwable t) { + notifyFailed(t); + throw Throwables.propagate(t); + } finally { + lock.unlock(); + } + } + }); + } + + @Override protected final void doStop() { + runningTask.cancel(false); + executorService.execute(new Runnable() { + @Override public void run() { + try { + lock.lock(); + try { + if (state() != State.STOPPING) { + // This means that the state has changed since we were scheduled. This implies that + // an execution of runOneIteration has thrown an exception and we have transitioned + // to a failed state, also this means that shutDown has already been called, so we + // do not want to call it again. + return; + } + shutDown(); + } finally { + lock.unlock(); + } + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + throw Throwables.propagate(t); + } + } + }); + } + }; + + /** + * Run one iteration of the scheduled task. If any invocation of this method throws an exception, + * the service will transition to the {@link Service.State#FAILED} state and this method will no + * longer be called. + */ + protected abstract void runOneIteration() throws Exception; + + /** + * Start the service. + * + *

    By default this method does nothing. + */ + protected void startUp() throws Exception {} + + /** + * Stop the service. This is guaranteed not to run concurrently with {@link #runOneIteration}. + * + *

    By default this method does nothing. + */ + protected void shutDown() throws Exception {} + + /** + * Returns the {@link Scheduler} object used to configure this service. This method will only be + * called once. + */ + protected abstract Scheduler scheduler(); + + /** + * Returns the {@link ScheduledExecutorService} that will be used to execute the {@link #startUp}, + * {@link #runOneIteration} and {@link #shutDown} methods. The executor will not be + * {@link ScheduledExecutorService#shutdown} when this service stops. Subclasses may override this + * method to use a custom {@link ScheduledExecutorService} instance. + * + *

    By default this returns a new {@link ScheduledExecutorService} with a single thread thread + * pool. This method will only be called once. + */ + protected ScheduledExecutorService executor() { + return Executors.newSingleThreadScheduledExecutor(); + } + + @Override public String toString() { + return getClass().getSimpleName() + " [" + state() + "]"; + } + + // We override instead of using ForwardingService so that these can be final. + + @Override public final ListenableFuture start() { + return delegate.start(); + } + + @Override public final State startAndWait() { + return delegate.startAndWait(); + } + + @Override public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override public final State state() { + return delegate.state(); + } + + @Override public final ListenableFuture stop() { + return delegate.stop(); + } + + @Override public final State stopAndWait() { + return delegate.stopAndWait(); + } + + @Override public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + /** + * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to + * use a dynamically changing schedule. After every execution of the task, assuming it hasn't + * been cancelled, the {@link #getNextSchedule} method will be called. + * + * @author Luke Sandberg + * @since 11.0 + */ + @Beta + public abstract static class CustomScheduler extends Scheduler { + + /** + * A callable class that can reschedule itself using a {@link CustomScheduler}. + */ + private class ReschedulableCallable extends ForwardingFuture implements Callable { + + /** The underlying task. */ + private final Runnable wrappedRunnable; + + /** The executor on which this Callable will be scheduled. */ + private final ScheduledExecutorService executor; + + /** + * The service that is managing this callable. This is used so that failure can be + * reported properly. + */ + private final AbstractService service; + + /** + * This lock is used to ensure safe and correct cancellation, it ensures that a new task is + * not scheduled while a cancel is ongoing. Also it protects the currentFuture variable to + * ensure that it is assigned atomically with being scheduled. + */ + private final ReentrantLock lock = new ReentrantLock(); + + /** The future that represents the next execution of this task.*/ + @GuardedBy("lock") + private Future currentFuture; + + ReschedulableCallable(AbstractService service, ScheduledExecutorService executor, + Runnable runnable) { + this.wrappedRunnable = runnable; + this.executor = executor; + this.service = service; + } + + @Override + public Void call() throws Exception { + wrappedRunnable.run(); + reschedule(); + return null; + } + + /** + * Atomically reschedules this task and assigns the new future to {@link #currentFuture}. + */ + public void reschedule() { + // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that + // cancel calls cancel on the correct future. 2. we want to make sure that the assignment + // to currentFuture doesn't race with itself so that currentFuture is assigned in the + // correct order. + lock.lock(); + try { + if (currentFuture == null || !currentFuture.isCancelled()) { + final Schedule schedule = CustomScheduler.this.getNextSchedule(); + currentFuture = executor.schedule(this, schedule.delay, schedule.unit); + } + } catch (Throwable e) { + // If an exception is thrown by the subclass then we need to make sure that the service + // notices and transitions to the FAILED state. We do it by calling notifyFailed directly + // because the service does not monitor the state of the future so if the exception is not + // caught and forwarded to the service the task would stop executing but the service would + // have no idea. + service.notifyFailed(e); + } finally { + lock.unlock(); + } + } + + // N.B. Only protect cancel and isCancelled because those are the only methods that are + // invoked by the AbstractScheduledService. + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + // Ensure that a task cannot be rescheduled while a cancel is ongoing. + lock.lock(); + try { + return currentFuture.cancel(mayInterruptIfRunning); + } finally { + lock.unlock(); + } + } + + @Override + protected Future delegate() { + throw new UnsupportedOperationException("Only cancel is supported by this future"); + } + } + + @Override + final Future schedule(AbstractService service, ScheduledExecutorService executor, + Runnable runnable) { + ReschedulableCallable task = new ReschedulableCallable(service, executor, runnable); + task.reschedule(); + return task; + } + + /** + * A value object that represents an absolute delay until a task should be invoked. + * + * @author Luke Sandberg + * @since 11.0 + */ + @Beta + protected static final class Schedule { + + private final long delay; + private final TimeUnit unit; + + /** + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + */ + public Schedule(long delay, TimeUnit unit) { + this.delay = delay; + this.unit = Preconditions.checkNotNull(unit); + } + } + + /** + * Calculates the time at which to next invoke the task. + * + *

    This is guaranteed to be called immediately after the task has completed an iteration and + * on the same thread as the previous execution of {@link + * AbstractScheduledService#runOneIteration}. + * + * @return a schedule that defines the delay before the next execution. + */ + protected abstract Schedule getNextSchedule() throws Exception; + } +} diff --git a/guava/src/com/google/common/util/concurrent/AbstractService.java b/guava/src/com/google/common/util/concurrent/AbstractService.java new file mode 100644 index 0000000..a2e14fe --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AbstractService.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; +import com.google.common.util.concurrent.Service.State; // javadoc needs this + +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.Immutable; + +/** + * Base class for implementing services that can handle {@link #doStart} and {@link #doStop} + * requests, responding to them with {@link #notifyStarted()} and {@link #notifyStopped()} + * callbacks. Its subclasses must manage threads manually; consider + * {@link AbstractExecutionThreadService} if you need only a single execution thread. + * + * @author Jesse Wilson + * @author Luke Sandberg + * @since 1.0 + */ +@Beta +public abstract class AbstractService implements Service { + private static final Logger logger = Logger.getLogger(AbstractService.class.getName()); + private final ReentrantLock lock = new ReentrantLock(); + + private final Transition startup = new Transition(); + private final Transition shutdown = new Transition(); + + /** + * The listeners to notify during a state transition. + */ + @GuardedBy("lock") + private final List listeners = Lists.newArrayList(); + + /** + * The queue of listeners that are waiting to be executed. + * + *

    Enqueue operations should be protected by {@link #lock} while dequeue operations should be + * protected by the implicit lock on this object. Dequeue operations should be executed atomically + * with the execution of the {@link Runnable} and additionally the {@link #lock} should not be + * held when the listeners are being executed. Use {@link #executeListeners} for this operation. + * This is necessary to ensure that elements on the queue are executed in the correct order. + * Enqueue operations should be protected so that listeners are added in the correct order. We use + * a concurrent queue implementation so that enqueues can be executed concurrently with dequeues. + */ + @GuardedBy("queuedListeners") + private final Queue queuedListeners = Queues.newConcurrentLinkedQueue(); + + /** + * The current state of the service. This should be written with the lock held but can be read + * without it because it is an immutable object in a volatile field. This is desirable so that + * methods like {@link #state}, {@link #failureCause} and notably {@link #toString} can be run + * without grabbing the lock. + * + *

    To update this field correctly the lock must be held to guarantee that the state is + * consistent. + */ + @GuardedBy("lock") + private volatile StateSnapshot snapshot = new StateSnapshot(State.NEW); + + protected AbstractService() { + // Add a listener to update the futures. This needs to be added first so that it is executed + // before the other listeners. This way the other listeners can access the completed futures. + addListener( + new Listener() { + @Override public void starting() {} + + @Override public void running() { + startup.set(State.RUNNING); + } + + @Override public void stopping(State from) { + if (from == State.STARTING) { + startup.set(State.STOPPING); + } + } + + @Override public void terminated(State from) { + if (from == State.NEW) { + startup.set(State.TERMINATED); + } + shutdown.set(State.TERMINATED); + } + + @Override public void failed(State from, Throwable failure) { + switch (from) { + case STARTING: + startup.setException(failure); + shutdown.setException(new Exception("Service failed to start.", failure)); + break; + case RUNNING: + shutdown.setException(new Exception("Service failed while running", failure)); + break; + case STOPPING: + shutdown.setException(failure); + break; + case TERMINATED: /* fall-through */ + case FAILED: /* fall-through */ + case NEW: /* fall-through */ + default: + throw new AssertionError("Unexpected from state: " + from); + } + } + }, + MoreExecutors.sameThreadExecutor()); + } + + /** + * This method is called by {@link #start} to initiate service startup. The invocation of this + * method should cause a call to {@link #notifyStarted()}, either during this method's run, or + * after it has returned. If startup fails, the invocation should cause a call to + * {@link #notifyFailed(Throwable)} instead. + * + *

    This method should return promptly; prefer to do work on a different thread where it is + * convenient. It is invoked exactly once on service startup, even when {@link #start} is called + * multiple times. + */ + protected abstract void doStart(); + + /** + * This method should be used to initiate service shutdown. The invocation of this method should + * cause a call to {@link #notifyStopped()}, either during this method's run, or after it has + * returned. If shutdown fails, the invocation should cause a call to + * {@link #notifyFailed(Throwable)} instead. + * + *

    This method should return promptly; prefer to do work on a different thread where it is + * convenient. It is invoked exactly once on service shutdown, even when {@link #stop} is called + * multiple times. + */ + protected abstract void doStop(); + + @Override + public final ListenableFuture start() { + lock.lock(); + try { + if (snapshot.state == State.NEW) { + snapshot = new StateSnapshot(State.STARTING); + starting(); + doStart(); + } + } catch (Throwable startupFailure) { + notifyFailed(startupFailure); + } finally { + lock.unlock(); + executeListeners(); + } + + return startup; + } + + @Override + public final ListenableFuture stop() { + lock.lock(); + try { + switch (snapshot.state) { + case NEW: + snapshot = new StateSnapshot(State.TERMINATED); + terminated(State.NEW); + break; + case STARTING: + snapshot = new StateSnapshot(State.STARTING, true, null); + stopping(State.STARTING); + break; + case RUNNING: + snapshot = new StateSnapshot(State.STOPPING); + stopping(State.RUNNING); + doStop(); + break; + case STOPPING: + case TERMINATED: + case FAILED: + // do nothing + break; + default: + throw new AssertionError("Unexpected state: " + snapshot.state); + } + } catch (Throwable shutdownFailure) { + notifyFailed(shutdownFailure); + } finally { + lock.unlock(); + executeListeners(); + } + + return shutdown; + } + + @Override + public State startAndWait() { + return Futures.getUnchecked(start()); + } + + @Override + public State stopAndWait() { + return Futures.getUnchecked(stop()); + } + + /** + * Implementing classes should invoke this method once their service has started. It will cause + * the service to transition from {@link State#STARTING} to {@link State#RUNNING}. + * + * @throws IllegalStateException if the service is not {@link State#STARTING}. + */ + protected final void notifyStarted() { + lock.lock(); + try { + if (snapshot.state != State.STARTING) { + IllegalStateException failure = new IllegalStateException( + "Cannot notifyStarted() when the service is " + snapshot.state); + notifyFailed(failure); + throw failure; + } + + if (snapshot.shutdownWhenStartupFinishes) { + snapshot = new StateSnapshot(State.STOPPING); + // We don't call listeners here because we already did that when we set the + // shutdownWhenStartupFinishes flag. + doStop(); + } else { + snapshot = new StateSnapshot(State.RUNNING); + running(); + } + } finally { + lock.unlock(); + executeListeners(); + } + } + + /** + * Implementing classes should invoke this method once their service has stopped. It will cause + * the service to transition from {@link State#STOPPING} to {@link State#TERMINATED}. + * + * @throws IllegalStateException if the service is neither {@link State#STOPPING} nor + * {@link State#RUNNING}. + */ + protected final void notifyStopped() { + lock.lock(); + try { + if (snapshot.state != State.STOPPING && snapshot.state != State.RUNNING) { + IllegalStateException failure = new IllegalStateException( + "Cannot notifyStopped() when the service is " + snapshot.state); + notifyFailed(failure); + throw failure; + } + State previous = snapshot.state; + snapshot = new StateSnapshot(State.TERMINATED); + terminated(previous); + } finally { + lock.unlock(); + executeListeners(); + } + } + + /** + * Invoke this method to transition the service to the {@link State#FAILED}. The service will + * not be stopped if it is running. Invoke this method when a service has failed critically + * or otherwise cannot be started nor stopped. + */ + protected final void notifyFailed(Throwable cause) { + checkNotNull(cause); + + lock.lock(); + try { + switch (snapshot.state) { + case NEW: + case TERMINATED: + throw new IllegalStateException("Failed while in state:" + snapshot.state, cause); + case RUNNING: + case STARTING: + case STOPPING: + State previous = snapshot.state; + snapshot = new StateSnapshot(State.FAILED, false, cause); + failed(previous, cause); + break; + case FAILED: + // Do nothing + break; + default: + throw new AssertionError("Unexpected state: " + snapshot.state); + } + } finally { + lock.unlock(); + executeListeners(); + } + } + + @Override + public final boolean isRunning() { + return state() == State.RUNNING; + } + + @Override + public final State state() { + return snapshot.externalState(); + } + + @Override + public final void addListener(Listener listener, Executor executor) { + checkNotNull(listener, "listener"); + checkNotNull(executor, "executor"); + lock.lock(); + try { + if (snapshot.state != State.TERMINATED && snapshot.state != State.FAILED) { + listeners.add(new ListenerExecutorPair(listener, executor)); + } + } finally { + lock.unlock(); + } + } + + @Override public String toString() { + return getClass().getSimpleName() + " [" + state() + "]"; + } + + /** + * A change from one service state to another, plus the result of the change. + */ + private class Transition extends AbstractFuture { + @Override + public State get(long timeout, TimeUnit unit) + throws InterruptedException, TimeoutException, ExecutionException { + try { + return super.get(timeout, unit); + } catch (TimeoutException e) { + throw new TimeoutException(AbstractService.this.toString()); + } + } + } + + /** + * Attempts to execute all the listeners in {@link #queuedListeners} while not holding the + * {@link #lock}. + */ + private void executeListeners() { + if (!lock.isHeldByCurrentThread()) { + synchronized (queuedListeners) { + Runnable listener; + while ((listener = queuedListeners.poll()) != null) { + listener.run(); + } + } + } + } + + @GuardedBy("lock") + private void starting() { + for (final ListenerExecutorPair pair : listeners) { + queuedListeners.add(new Runnable() { + @Override public void run() { + pair.execute(new Runnable() { + @Override public void run() { + pair.listener.starting(); + } + }); + } + }); + } + } + + @GuardedBy("lock") + private void running() { + for (final ListenerExecutorPair pair : listeners) { + queuedListeners.add(new Runnable() { + @Override public void run() { + pair.execute(new Runnable() { + @Override public void run() { + pair.listener.running(); + } + }); + } + }); + } + } + + @GuardedBy("lock") + private void stopping(final State from) { + for (final ListenerExecutorPair pair : listeners) { + queuedListeners.add(new Runnable() { + @Override public void run() { + pair.execute(new Runnable() { + @Override public void run() { + pair.listener.stopping(from); + } + }); + } + }); + } + } + + @GuardedBy("lock") + private void terminated(final State from) { + for (final ListenerExecutorPair pair : listeners) { + queuedListeners.add(new Runnable() { + @Override public void run() { + pair.execute(new Runnable() { + @Override public void run() { + pair.listener.terminated(from); + } + }); + } + }); + } + // There are no more state transitions so we can clear this out. + listeners.clear(); + } + + @GuardedBy("lock") + private void failed(final State from, final Throwable cause) { + for (final ListenerExecutorPair pair : listeners) { + queuedListeners.add(new Runnable() { + @Override public void run() { + pair.execute(new Runnable() { + @Override public void run() { + pair.listener.failed(from, cause); + } + }); + } + }); + } + // There are no more state transitions so we can clear this out. + listeners.clear(); + } + + /** A simple holder for a listener and its executor. */ + private static class ListenerExecutorPair { + final Listener listener; + final Executor executor; + + ListenerExecutorPair(Listener listener, Executor executor) { + this.listener = listener; + this.executor = executor; + } + + /** + * Executes the given {@link Runnable} on {@link #executor} logging and swallowing all + * exceptions + */ + void execute(Runnable runnable) { + try { + executor.execute(runnable); + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception while executing listener " + listener + + " with executor " + executor, e); + } + } + } + + /** + * An immutable snapshot of the current state of the service. This class represents a consistent + * snapshot of the state and therefore it can be used to answer simple queries without needing to + * grab a lock. + */ + @Immutable + private static final class StateSnapshot { + /** + * The internal state, which equals external state unless + * shutdownWhenStartupFinishes is true. + */ + final State state; + + /** + * If true, the user requested a shutdown while the service was still starting + * up. + */ + final boolean shutdownWhenStartupFinishes; + + /** + * The exception that caused this service to fail. This will be {@code null} + * unless the service has failed. + */ + @Nullable + final Throwable failure; + + StateSnapshot(State internalState) { + this(internalState, false, null); + } + + StateSnapshot(State internalState, boolean shutdownWhenStartupFinishes, Throwable failure) { + checkArgument(!shutdownWhenStartupFinishes || internalState == State.STARTING, + "shudownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.", + internalState); + checkArgument(!(failure != null ^ internalState == State.FAILED), + "A failure cause should be set if and only if the state is failed. Got %s and %s " + + "instead.", internalState, failure); + this.state = internalState; + this.shutdownWhenStartupFinishes = shutdownWhenStartupFinishes; + this.failure = failure; + } + + /** @see Service#state() */ + State externalState() { + if (shutdownWhenStartupFinishes && state == State.STARTING) { + return State.STOPPING; + } else { + return state; + } + } + + /** @see Service#failureCause() */ + Throwable failureCause() { + checkState(state == State.FAILED, + "failureCause() is only valid if the service has failed, service is %s", state); + return failure; + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/AsyncFunction.java b/guava/src/com/google/common/util/concurrent/AsyncFunction.java new file mode 100644 index 0000000..441c029 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AsyncFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.Future; + +/** + * Transforms a value, possibly asynchronously. For an example usage and more + * information, see {@link Futures#transform(ListenableFuture, AsyncFunction)}. + * + * @author Chris Povirk + * @since 11.0 + */ +@Beta +public interface AsyncFunction { + /** + * Returns an output {@code Future} to use in place of the given {@code + * input}. The output {@code Future} need not be {@linkplain Future#isDone + * done}, making {@code AsyncFunction} suitable for asynchronous derivations. + * + *

    Throwing an exception from this method is equivalent to returning a + * failing {@code Future}. + */ + ListenableFuture apply(I input) throws Exception; +} diff --git a/guava/src/com/google/common/util/concurrent/AtomicDouble.java b/guava/src/com/google/common/util/concurrent/AtomicDouble.java new file mode 100644 index 0000000..615d7a9 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AtomicDouble.java @@ -0,0 +1,257 @@ +/* + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/extra/AtomicDouble.java?revision=1.13 + * (Modified to adapt to guava coding conventions and + * to use AtomicLongFieldUpdater instead of sun.misc.Unsafe) + */ + +package com.google.common.util.concurrent; + +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.longBitsToDouble; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +/** + * A {@code double} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An {@code + * AtomicDouble} is used in applications such as atomic accumulation, + * and cannot be used as a replacement for a {@link Double}. However, + * this class does extend {@code Number} to allow uniform access by + * tools and utilities that deal with numerically-based classes. + * + *

    This class compares primitive {@code double} + * values in methods such as {@link #compareAndSet} by comparing their + * bitwise representation using {@link Double#doubleToRawLongBits}, + * which differs from both the primitive double {@code ==} operator + * and from {@link Double#equals}, as if implemented by: + *

     {@code
    + * static boolean bitEquals(double x, double y) {
    + *   long xBits = Double.doubleToRawLongBits(x);
    + *   long yBits = Double.doubleToRawLongBits(y);
    + *   return xBits == yBits;
    + * }}
    + * + *

    It is possible to write a more scalable updater, at the cost of + * giving up strict atomicity. See for example + * + * and + * . + * + * @author Doug Lea + * @author Martin Buchholz + * @since 11.0 + */ +@Beta +public class AtomicDouble extends Number implements java.io.Serializable { + private static final long serialVersionUID = 0L; + + private transient volatile long value; + + private static final AtomicLongFieldUpdater updater = + AtomicLongFieldUpdater.newUpdater(AtomicDouble.class, "value"); + + /** + * Creates a new {@code AtomicDouble} with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicDouble(double initialValue) { + value = doubleToRawLongBits(initialValue); + } + + /** + * Creates a new {@code AtomicDouble} with initial value {@code 0.0}. + */ + public AtomicDouble() { + // assert doubleToRawLongBits(0.0) == 0L; + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final double get() { + return longBitsToDouble(value); + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(double newValue) { + long next = doubleToRawLongBits(newValue); + value = next; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + */ + public final void lazySet(double newValue) { + set(newValue); + // TODO(user): replace with code below when jdk5 support is dropped. + // long next = doubleToRawLongBits(newValue); + // updater.lazySet(this, next); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final double getAndSet(double newValue) { + long next = doubleToRawLongBits(newValue); + return longBitsToDouble(updater.getAndSet(this, next)); + } + + /** + * Atomically sets the value to the given updated value + * if the current value is bitwise equal + * to the expected value. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful. False return indicates that + * the actual value was not bitwise equal to the expected value. + */ + public final boolean compareAndSet(double expect, double update) { + return updater.compareAndSet(this, + doubleToRawLongBits(expect), + doubleToRawLongBits(update)); + } + + /** + * Atomically sets the value to the given updated value + * if the current value is bitwise equal + * to the expected value. + * + *

    May + * fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful + */ + public final boolean weakCompareAndSet(double expect, double update) { + return updater.weakCompareAndSet(this, + doubleToRawLongBits(expect), + doubleToRawLongBits(update)); + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final double getAndAdd(double delta) { + while (true) { + long current = value; + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (updater.compareAndSet(this, current, next)) { + return currentVal; + } + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final double addAndGet(double delta) { + while (true) { + long current = value; + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (updater.compareAndSet(this, current, next)) { + return nextVal; + } + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value + */ + public String toString() { + return Double.toString(get()); + } + + /** + * Returns the value of this {@code AtomicDouble} as an {@code int} + * after a narrowing primitive conversion. + */ + public int intValue() { + return (int) get(); + } + + /** + * Returns the value of this {@code AtomicDouble} as a {@code long} + * after a narrowing primitive conversion. + */ + public long longValue() { + return (long) get(); + } + + /** + * Returns the value of this {@code AtomicDouble} as a {@code float} + * after a narrowing primitive conversion. + */ + public float floatValue() { + return (float) get(); + } + + /** + * Returns the value of this {@code AtomicDouble} as a {@code double}. + */ + public double doubleValue() { + return get(); + } + + /** + * Saves the state to a stream (that is, serializes it). + * + * @serialData The current value is emitted (a {@code double}). + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + + s.writeDouble(get()); + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + set(s.readDouble()); + } +} diff --git a/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java new file mode 100644 index 0000000..8da0195 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java @@ -0,0 +1,271 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/extra/AtomicDoubleArray.java?revision=1.5 + * (Modified to adapt to guava coding conventions and + * to use AtomicLongArray instead of sun.misc.Unsafe) + */ + +package com.google.common.util.concurrent; + +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.longBitsToDouble; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.atomic.AtomicLongArray; + +/** + * A {@code double} array in which elements may be updated atomically. + * See the {@link java.util.concurrent.atomic} package specification + * for description of the properties of atomic variables. + * + *

    This class compares primitive {@code double} + * values in methods such as {@link #compareAndSet} by comparing their + * bitwise representation using {@link Double#doubleToRawLongBits}, + * which differs from both the primitive double {@code ==} operator + * and from {@link Double#equals}, as if implemented by: + *

     {@code
    + * static boolean bitEquals(double x, double y) {
    + *   long xBits = Double.doubleToRawLongBits(x);
    + *   long yBits = Double.doubleToRawLongBits(y);
    + *   return xBits == yBits;
    + * }}
    + * + * @author Doug Lea + * @author Martin Buchholz + * @since 11.0 + */ +@Beta +public class AtomicDoubleArray implements java.io.Serializable { + private static final long serialVersionUID = 0L; + + // Making this non-final is the lesser evil according to Effective + // Java 2nd Edition Item 76: Write readObject methods defensively. + private transient AtomicLongArray longs; + + /** + * Creates a new {@code AtomicDoubleArray} of the given length, + * with all elements initially zero. + * + * @param length the length of the array + */ + public AtomicDoubleArray(int length) { + this.longs = new AtomicLongArray(length); + } + + /** + * Creates a new {@code AtomicDoubleArray} with the same length + * as, and all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicDoubleArray(double[] array) { + final int len = array.length; + long[] longArray = new long[len]; + for (int i = 0; i < len; i++) { + longArray[i] = doubleToRawLongBits(array[i]); + } + this.longs = new AtomicLongArray(longArray); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return longs.length(); + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final double get(int i) { + return longBitsToDouble(longs.get(i)); + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, double newValue) { + long next = doubleToRawLongBits(newValue); + longs.set(i, next); + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void lazySet(int i, double newValue) { + set(i, newValue); + // TODO(user): replace with code below when jdk5 support is dropped. + // long next = doubleToRawLongBits(newValue); + // longs.lazySet(i, next); + } + + /** + * Atomically sets the element at position {@code i} to the given value + * and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final double getAndSet(int i, double newValue) { + long next = doubleToRawLongBits(newValue); + return longBitsToDouble(longs.getAndSet(i, next)); + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value + * if the current value is bitwise equal + * to the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, double expect, double update) { + return longs.compareAndSet(i, + doubleToRawLongBits(expect), + doubleToRawLongBits(update)); + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value + * if the current value is bitwise equal + * to the expected value. + * + *

    May + * fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful + */ + public final boolean weakCompareAndSet(int i, double expect, double update) { + return longs.weakCompareAndSet(i, + doubleToRawLongBits(expect), + doubleToRawLongBits(update)); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + public final double getAndAdd(int i, double delta) { + while (true) { + long current = longs.get(i); + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (longs.compareAndSet(i, current, next)) { + return currentVal; + } + } + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + public double addAndGet(int i, double delta) { + while (true) { + long current = longs.get(i); + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (longs.compareAndSet(i, current, next)) { + return nextVal; + } + } + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = length() - 1; + if (iMax == -1) { + return "[]"; + } + + // Double.toString(Math.PI).length() == 17 + StringBuilder b = new StringBuilder((17 + 2) * (iMax + 1)); + b.append('['); + for (int i = 0;; i++) { + b.append(longBitsToDouble(longs.get(i))); + if (i == iMax) { + return b.append(']').toString(); + } + b.append(',').append(' '); + } + } + + /** + * Saves the state to a stream (that is, serializes it). + * + * @serialData The length of the array is emitted (int), followed by all + * of its elements (each a {@code double}) in the proper order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + + // Write out array length + int length = length(); + s.writeInt(length); + + // Write out all elements in the proper order. + for (int i = 0; i < length; i++) { + s.writeDouble(get(i)); + } + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Read in array length and allocate array + int length = s.readInt(); + this.longs = new AtomicLongArray(length); + + // Read in all elements in the proper order. + for (int i = 0; i < length; i++) { + set(i, s.readDouble()); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/AtomicLongMap.java b/guava/src/com/google/common/util/concurrent/AtomicLongMap.java new file mode 100644 index 0000000..f5aafd0 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/AtomicLongMap.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A map containing {@code long} values that can be atomically updated. While writes to a + * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map + * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with + * {@code K}. If a key has not yet been associated with a value, its implicit value is zero. + * + *

    Most methods in this class treat absent values and zero values identically, as individually + * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, + * {@link #asMap}, and {@link #toString}. + * + *

    Instances of this class may be used by multiple threads concurrently. All operations are + * atomic unless otherwise noted. + * + *

    Note: If your values are always positive and less than 2^31, you may wish to use a + * {@link com.google.common.collect.Multiset} such as + * {@link com.google.common.collect.ConcurrentHashMultiset} instead. + * + * Warning: Unlike {@code Multiset}, entries whose values are zero are not automatically + * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}. + * + * @author Charles Fry + * @since 11.0 + */ +@Beta +@GwtCompatible +public final class AtomicLongMap { + private final ConcurrentHashMap map; + + private AtomicLongMap(ConcurrentHashMap map) { + this.map = checkNotNull(map); + } + + /** + * Creates an {@code AtomicLongMap}. + */ + public static AtomicLongMap create() { + return new AtomicLongMap(new ConcurrentHashMap()); + } + + /** + * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. + */ + public static AtomicLongMap create(Map m) { + AtomicLongMap result = create(); + result.putAll(m); + return result; + } + + /** + * Returns the value associated with {@code key}, or zero if there is no value associated with + * {@code key}. + */ + public long get(K key) { + AtomicLong atomic = map.get(key); + return atomic == null ? 0L : atomic.get(); + } + + /** + * Increments by one the value currently associated with {@code key}, and returns the new value. + */ + public long incrementAndGet(K key) { + return addAndGet(key, 1); + } + + /** + * Decrements by one the value currently associated with {@code key}, and returns the new value. + */ + public long decrementAndGet(K key) { + return addAndGet(key, -1); + } + + /** + * Adds {@code delta} to the value currently associated with {@code key}, and returns the new + * value. + */ + public long addAndGet(K key, long delta) { + outer: for (;;) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + atomic = map.putIfAbsent(key, new AtomicLong(delta)); + if (atomic == null) { + return delta; + } + // atomic is now non-null; fall through + } + + for (;;) { + long oldValue = atomic.get(); + if (oldValue == 0L) { + // don't compareAndSet a zero + if (map.replace(key, atomic, new AtomicLong(delta))) { + return delta; + } + // atomic replaced + continue outer; + } + + long newValue = oldValue + delta; + if (atomic.compareAndSet(oldValue, newValue)) { + return newValue; + } + // value changed + } + } + } + + /** + * Increments by one the value currently associated with {@code key}, and returns the old value. + */ + public long getAndIncrement(K key) { + return getAndAdd(key, 1); + } + + /** + * Decrements by one the value currently associated with {@code key}, and returns the old value. + */ + public long getAndDecrement(K key) { + return getAndAdd(key, -1); + } + + /** + * Adds {@code delta} to the value currently associated with {@code key}, and returns the old + * value. + */ + public long getAndAdd(K key, long delta) { + outer: for (;;) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + atomic = map.putIfAbsent(key, new AtomicLong(delta)); + if (atomic == null) { + return 0L; + } + // atomic is now non-null; fall through + } + + for (;;) { + long oldValue = atomic.get(); + if (oldValue == 0L) { + // don't compareAndSet a zero + if (map.replace(key, atomic, new AtomicLong(delta))) { + return 0L; + } + // atomic replaced + continue outer; + } + + long newValue = oldValue + delta; + if (atomic.compareAndSet(oldValue, newValue)) { + return oldValue; + } + // value changed + } + } + } + + /** + * Associates {@code newValue} with {@code key} in this map, and returns the value previously + * associated with {@code key}, or zero if there was no such value. + */ + public long put(K key, long newValue) { + outer: for (;;) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + atomic = map.putIfAbsent(key, new AtomicLong(newValue)); + if (atomic == null) { + return 0L; + } + // atomic is now non-null; fall through + } + + for (;;) { + long oldValue = atomic.get(); + if (oldValue == 0L) { + // don't compareAndSet a zero + if (map.replace(key, atomic, new AtomicLong(newValue))) { + return 0L; + } + // atomic replaced + continue outer; + } + + if (atomic.compareAndSet(oldValue, newValue)) { + return oldValue; + } + // value changed + } + } + } + + /** + * Copies all of the mappings from the specified map to this map. The effect of this call is + * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key + * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined + * if the specified map is modified while the operation is in progress. + */ + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes and returns the value associated with {@code key}. If {@code key} is not + * in the map, this method has no effect and returns zero. + */ + public long remove(K key) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + return 0L; + } + + for (;;) { + long oldValue = atomic.get(); + if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) { + // only remove after setting to zero, to avoid concurrent updates + map.remove(key, atomic); + // succeed even if the remove fails, since the value was already adjusted + return oldValue; + } + } + } + + /** + * Removes all mappings from this map whose values are zero. + * + *

    This method is not atomic: the map may be visible in intermediate states, where some + * of the zero values have been removed and others have not. + */ + public void removeAllZeros() { + for (K key : map.keySet()) { + AtomicLong atomic = map.get(key); + if (atomic != null && atomic.get() == 0L) { + map.remove(key, atomic); + } + } + } + + /** + * Returns the sum of all values in this map. + * + *

    This method is not atomic: the sum may or may not include other concurrent operations. + */ + public long sum() { + long sum = 0L; + for (AtomicLong value : map.values()) { + sum = sum + value.get(); + } + return sum; + } + + private transient Map asMap; + + /** + * Returns a live, read-only view of the map backing this {@code AtomicLongMap}. + */ + public Map asMap() { + Map result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + private Map createAsMap() { + return Collections.unmodifiableMap( + Maps.transformValues(map, new Function() { + @Override + public Long apply(AtomicLong atomic) { + return atomic.get(); + } + })); + } + + /** + * Returns true if this map contains a mapping for the specified key. + */ + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + /** + * Returns the number of key-value mappings in this map. If the map contains more than + * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. + */ + public int size() { + return map.size(); + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Removes all of the mappings from this map. The map will be empty after this call returns. + * + *

    This method is not atomic: the map may not be empty after returning if there were concurrent + * writes. + */ + public void clear() { + map.clear(); + } + + @Override + public String toString() { + return map.toString(); + } + + /* + * ConcurrentMap operations which we may eventually add. + * + * The problem with these is that remove(K, long) has to be done in two phases by definition --- + * first decrementing to zero, and then removing. putIfAbsent or replace could observe the + * intermediate zero-state. Ways we could deal with this are: + * + * - Don't define any of the ConcurrentMap operations. This is the current state of affairs. + * + * - Define putIfAbsent and replace as treating zero and absent identically (as currently + * implemented below). This is a bit surprising with putIfAbsent, which really becomes + * putIfZero. + * + * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement + * remove(K, long). Without any two-phase operations it becomes feasible for all remaining + * operations to distinguish between zero and absent. If we do this, then perhaps we should add + * replace(key, long). + * + * - Introduce a special-value private static final AtomicLong that would have the meaning of + * removal-in-progress, and rework all operations to properly distinguish between zero and + * absent. + */ + + /** + * If {@code key} is not already associated with a value or if {@code key} is associated with + * zero, associate it with {@code newValue}. Returns the previous value associated with + * {@code key}, or zero if there was no mapping for {@code key}. + */ + long putIfAbsent(K key, long newValue) { + for (;;) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + atomic = map.putIfAbsent(key, new AtomicLong(newValue)); + if (atomic == null) { + return 0L; + } + // atomic is now non-null; fall through + } + + long oldValue = atomic.get(); + if (oldValue == 0L) { + // don't compareAndSet a zero + if (map.replace(key, atomic, new AtomicLong(newValue))) { + return 0L; + } + // atomic replaced + continue; + } + + return oldValue; + } + } + + /** + * If {@code (key, expectedOldValue)} is currently in the map, this method replaces + * {@code expectedOldValue} with {@code newValue} and returns true; otherwise, this method + * returns false. + * + *

    If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} + * is currently in the map, or if {@code key} is not in the map at all. + */ + boolean replace(K key, long expectedOldValue, long newValue) { + if (expectedOldValue == 0L) { + return putIfAbsent(key, newValue) == 0L; + } else { + AtomicLong atomic = map.get(key); + return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue); + } + } + + /** + * If {@code (key, value)} is currently in the map, this method removes it and returns + * true; otherwise, this method returns false. + */ + boolean remove(K key, long value) { + AtomicLong atomic = map.get(key); + if (atomic == null) { + return false; + } + + long oldValue = atomic.get(); + if (oldValue != value) { + return false; + } + + if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) { + // only remove after setting to zero, to avoid concurrent updates + map.remove(key, atomic); + // succeed even if the remove fails, since the value was already adjusted + return true; + } + + // value changed + return false; + } + +} diff --git a/guava/src/com/google/common/util/concurrent/Atomics.java b/guava/src/com/google/common/util/concurrent/Atomics.java new file mode 100644 index 0000000..fece83d --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Atomics.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to classes in the + * {@code java.util.concurrent.atomic} package. + * + * @author Kurt Alfred Kluever + * @since 10.0 + */ +@Beta +public final class Atomics { + private Atomics() {} + + /** + * Creates an {@code AtomicReference} instance with no initial value. + * + * @return a new {@code AtomicReference} with no initial value + */ + public static AtomicReference newReference() { + return new AtomicReference(); + } + + /** + * Creates an {@code AtomicReference} instance with the given initial value. + * + * @param initialValue the initial value + * @return a new {@code AtomicReference} with the given initial value + */ + public static AtomicReference newReference(@Nullable V initialValue) { + return new AtomicReference(initialValue); + } + + /** + * Creates an {@code AtomicReferenceArray} instance of given length. + * + * @param length the length of the array + * @return a new {@code AtomicReferenceArray} with the given length + */ + public static AtomicReferenceArray newReferenceArray(int length) { + return new AtomicReferenceArray(length); + } + + /** + * Creates an {@code AtomicReferenceArray} instance with the same length as, + * and all elements copied from, the given array. + * + * @param array the array to copy elements from + * @return a new {@code AtomicReferenceArray} copied from the given array + */ + public static AtomicReferenceArray newReferenceArray(E[] array) { + return new AtomicReferenceArray(array); + } +} diff --git a/guava/src/com/google/common/util/concurrent/Callables.java b/guava/src/com/google/common/util/concurrent/Callables.java new file mode 100644 index 0000000..f970f42 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Callables.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.Callable; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to the {@link Callable} interface. + * + * @author Isaac Shum + * @since 1.0 + */ +public final class Callables { + private Callables() {} + + /** + * Creates a {@code Callable} which immediately returns a preset value each + * time it is called. + */ + public static Callable returning(final @Nullable T value) { + return new Callable() { + @Override public T call() { + return value; + } + }; + } +} diff --git a/guava/src/com/google/common/util/concurrent/CheckedFuture.java b/guava/src/com/google/common/util/concurrent/CheckedFuture.java new file mode 100644 index 0000000..31504e2 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/CheckedFuture.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A {@code CheckedFuture} is a {@link ListenableFuture} that includes versions + * of the {@code get} methods that can throw a checked exception. This makes it + * easier to create a future that executes logic which can throw an exception. + * + *

    A common implementation is {@link Futures#immediateCheckedFuture}. + * + *

    Implementations of this interface must adapt the exceptions thrown by + * {@code Future#get()}: {@link CancellationException}, + * {@link ExecutionException} and {@link InterruptedException} into the type + * specified by the {@code E} type parameter. + * + *

    This interface also extends the ListenableFuture interface to allow + * listeners to be added. This allows the future to be used as a normal + * {@link Future} or as an asynchronous callback mechanism as needed. This + * allows multiple callbacks to be registered for a particular task, and the + * future will guarantee execution of all listeners when the task completes. + * + *

    For a simpler alternative to CheckedFuture, consider accessing Future + * values with {@link Futures#get(Future, Class) Futures.get()}. + * + * @author Sven Mawson + * @since 1.0 + */ +@Beta +public interface CheckedFuture + extends ListenableFuture { + + /** + * Exception checking version of {@link Future#get()} that will translate + * {@link InterruptedException}, {@link CancellationException} and + * {@link ExecutionException} into application-specific exceptions. + * + * @return the result of executing the future. + * @throws X on interruption, cancellation or execution exceptions. + */ + V checkedGet() throws X; + + /** + * Exception checking version of {@link Future#get(long, TimeUnit)} that will + * translate {@link InterruptedException}, {@link CancellationException} and + * {@link ExecutionException} into application-specific exceptions. On + * timeout this method throws a normal {@link TimeoutException}. + * + * @return the result of executing the future. + * @throws TimeoutException if retrieving the result timed out. + * @throws X on interruption, cancellation or execution exceptions. + */ + V checkedGet(long timeout, TimeUnit unit) throws TimeoutException, X; +} diff --git a/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java b/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java new file mode 100644 index 0000000..53d19d0 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java @@ -0,0 +1,1034 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * The {@code CycleDetectingLockFactory} creates {@link ReentrantLock}s and + * {@link ReentrantReadWriteLock}s that detect potential deadlock by checking + * for cycles in lock acquisition order. + *

    + * Potential deadlocks detected when calling the {@code lock()}, + * {@code lockInterruptibly()}, or {@code tryLock()} methods will result in the + * execution of the {@link Policy} specified when creating the factory. The + * currently available policies are: + *

      + *
    • DISABLED + *
    • WARN + *
    • THROW + *
    + * The locks created by a factory instance will detect lock acquisition cycles + * with locks created by other {@code CycleDetectingLockFactory} instances + * (except those with {@code Policy.DISABLED}). A lock's behavior when a cycle + * is detected, however, is defined by the {@code Policy} of the factory that + * created it. This allows detection of cycles across components while + * delegating control over lock behavior to individual components. + *

    + * Applications are encouraged to use a {@code CycleDetectingLockFactory} to + * create any locks for which external/unmanaged code is executed while the lock + * is held. (See caveats under Performance). + *

    + * Cycle Detection + *

    + * Deadlocks can arise when locks are acquired in an order that forms a cycle. + * In a simple example involving two locks and two threads, deadlock occurs + * when one thread acquires Lock A, and then Lock B, while another thread + * acquires Lock B, and then Lock A: + *

    + * Thread1: acquire(LockA) --X acquire(LockB)
    + * Thread2: acquire(LockB) --X acquire(LockA)
    + * 
    + * Neither thread will progress because each is waiting for the other. In more + * complex applications, cycles can arise from interactions among more than 2 + * locks: + *
    + * Thread1: acquire(LockA) --X acquire(LockB)
    + * Thread2: acquire(LockB) --X acquire(LockC)
    + * ...
    + * ThreadN: acquire(LockN) --X acquire(LockA)
    + * 
    + * The implementation detects cycles by constructing a directed graph in which + * each lock represents a node and each edge represents an acquisition ordering + * between two locks. + *
      + *
    • Each lock adds (and removes) itself to/from a ThreadLocal Set of acquired + * locks when the Thread acquires its first hold (and releases its last + * remaining hold). + *
    • Before the lock is acquired, the lock is checked against the current set + * of acquired locks---to each of the acquired locks, an edge from the + * soon-to-be-acquired lock is either verified or created. + *
    • If a new edge needs to be created, the outgoing edges of the acquired + * locks are traversed to check for a cycle that reaches the lock to be + * acquired. If no cycle is detected, a new "safe" edge is created. + *
    • If a cycle is detected, an "unsafe" (cyclic) edge is created to represent + * a potential deadlock situation, and the appropriate Policy is executed. + *
    + * Note that detection of potential deadlock does not necessarily indicate that + * deadlock will happen, as it is possible that higher level application logic + * prevents the cyclic lock acquisition from occurring. One example of a false + * positive is: + *
    + * LockA -> LockB -> LockC
    + * LockA -> LockC -> LockB
    + * 
    + * + * ReadWriteLocks + *

    + * While {@code ReadWriteLock}s have different properties and can form cycles + * without potential deadlock, this class treats {@code ReadWriteLock}s as + * equivalent to traditional exclusive locks. Although this increases the false + * positives that the locks detect (i.e. cycles that will not actually result in + * deadlock), it simplifies the algorithm and implementation considerably. The + * assumption is that a user of this factory wishes to eliminate any cyclic + * acquisition ordering. + *

    + * Explicit Lock Acquisition Ordering + *

    + * The {@link CycleDetectingLockFactory.WithExplicitOrdering} class can be used + * to enforce an application-specific ordering in addition to performing general + * cycle detection. + *

    + * Garbage Collection + *

    + * In order to allow proper garbage collection of unused locks, the edges of + * the lock graph are weak references. + *

    + * Performance + *

    + * The extra bookkeeping done by cycle detecting locks comes at some cost to + * performance. Benchmarks (as of December 2011) show that: + * + *

      + *
    • for an unnested {@code lock()} and {@code unlock()}, a cycle detecting + * lock takes 38ns as opposed to the 24ns taken by a plain lock. + *
    • for nested locking, the cost increases with the depth of the nesting: + *
        + *
      • 2 levels: average of 64ns per lock()/unlock() + *
      • 3 levels: average of 77ns per lock()/unlock() + *
      • 4 levels: average of 99ns per lock()/unlock() + *
      • 5 levels: average of 103ns per lock()/unlock() + *
      • 10 levels: average of 184ns per lock()/unlock() + *
      • 20 levels: average of 393ns per lock()/unlock() + *
      + *
    + * + * As such, the CycleDetectingLockFactory may not be suitable for + * performance-critical applications which involve tightly-looped or + * deeply-nested locking algorithms. + * + * @author Darick Tong + * @since 13.0 + */ +@Beta +@ThreadSafe +public class CycleDetectingLockFactory { + + /** + * Encapsulates the action to be taken when a potential deadlock is + * encountered. Clients can use one of the predefined {@link Policies} or + * specify a custom implementation. Implementations must be thread-safe. + * + * @since 13.0 + */ + @Beta + @ThreadSafe + public interface Policy { + + /** + * Called when a potential deadlock is encountered. Implementations can + * throw the given {@code exception} and/or execute other desired logic. + *

    + * Note that the method will be called even upon an invocation of + * {@code tryLock()}. Although {@code tryLock()} technically recovers from + * deadlock by eventually timing out, this behavior is chosen based on the + * assumption that it is the application's wish to prohibit any cyclical + * lock acquisitions. + */ + void handlePotentialDeadlock(PotentialDeadlockException exception); + } + + /** + * Pre-defined {@link Policy} implementations. + * + * @since 13.0 + */ + @Beta + public enum Policies implements Policy { + /** + * When potential deadlock is detected, this policy results in the throwing + * of the {@code PotentialDeadlockException} indicating the potential + * deadlock, which includes stack traces illustrating the cycle in lock + * acquisition order. + */ + THROW { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) { + throw e; + } + }, + + /** + * When potential deadlock is detected, this policy results in the logging + * of a {@link Level#SEVERE} message indicating the potential deadlock, + * which includes stack traces illustrating the cycle in lock acquisition + * order. + */ + WARN { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) { + logger.log(Level.SEVERE, "Detected potential deadlock", e); + } + }, + + /** + * Disables cycle detection. This option causes the factory to return + * unmodified lock implementations provided by the JDK, and is provided to + * allow applications to easily parameterize when cycle detection is + * enabled. + *

    + * Note that locks created by a factory with this policy will not + * participate the cycle detection performed by locks created by other + * factories. + */ + DISABLED { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) { + } + }; + } + + /** + * Creates a new factory with the specified policy. + */ + public static CycleDetectingLockFactory newInstance(Policy policy) { + return new CycleDetectingLockFactory(policy); + } + + /** + * Equivalent to {@code newReentrantLock(lockName, false)}. + */ + public ReentrantLock newReentrantLock(String lockName) { + return newReentrantLock(lockName, false); + } + + /** + * Creates a {@link ReentrantLock} with the given fairness policy. The + * {@code lockName} is used in the warning or exception output to help + * identify the locks involved in the detected deadlock. + */ + public ReentrantLock newReentrantLock(String lockName, boolean fair) { + return policy == Policies.DISABLED ? new ReentrantLock(fair) + : new CycleDetectingReentrantLock( + new LockGraphNode(lockName), fair); + } + + /** + * Equivalent to {@code newReentrantReadWriteLock(lockName, false)}. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock(String lockName) { + return newReentrantReadWriteLock(lockName, false); + } + + /** + * Creates a {@link ReentrantReadWriteLock} with the given fairness policy. + * The {@code lockName} is used in the warning or exception output to help + * identify the locks involved in the detected deadlock. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock( + String lockName, boolean fair) { + return policy == Policies.DISABLED ? new ReentrantReadWriteLock(fair) + : new CycleDetectingReentrantReadWriteLock( + new LockGraphNode(lockName), fair); + } + + // A static mapping from an Enum type to its set of LockGraphNodes. + private static final Map, + Map> lockGraphNodesPerType = + new MapMaker().weakKeys().makeComputingMap( + new OrderedLockGraphNodesCreator()); + + /** + * Creates a {@code CycleDetectingLockFactory.WithExplicitOrdering}. + */ + public static > WithExplicitOrdering + newInstanceWithExplicitOrdering(Class enumClass, Policy policy) { + // OrderedLockGraphNodesCreator maps each enumClass to a Map with the + // corresponding enum key type. + @SuppressWarnings("unchecked") + Map lockGraphNodes = + (Map) lockGraphNodesPerType.get(enumClass); + return new WithExplicitOrdering(policy, lockGraphNodes); + } + + /** + * A {@code CycleDetectingLockFactory.WithExplicitOrdering} provides the + * additional enforcement of an application-specified ordering of lock + * acquisitions. The application defines the allowed ordering with an + * {@code Enum} whose values each correspond to a lock type. The order in + * which the values are declared dictates the allowed order of lock + * acquisition. In other words, locks corresponding to smaller values of + * {@link Enum#ordinal()} should only be acquired before locks with larger + * ordinals. Example: + * + *

       {@code
    +   * enum MyLockOrder {
    +   *   FIRST, SECOND, THIRD;
    +   * }
    +   *
    +   * CycleDetectingLockFactory.WithExplicitOrdering factory =
    +   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(Policies.THROW);
    +   *
    +   * Lock lock1 = factory.newReentrantLock(MyLockOrder.FIRST);
    +   * Lock lock2 = factory.newReentrantLock(MyLockOrder.SECOND);
    +   * Lock lock3 = factory.newReentrantLock(MyLockOrder.THIRD);
    +   *
    +   * lock1.lock();
    +   * lock3.lock();
    +   * lock2.lock();  // will throw an IllegalStateException
    +   * }
    + * + * As with all locks created by instances of {@code CycleDetectingLockFactory} + * explicitly ordered locks participate in general cycle detection with all + * other cycle detecting locks, and a lock's behavior when detecting a cyclic + * lock acquisition is defined by the {@code Policy} of the factory that + * created it. + *

    + * Note, however, that although multiple locks can be created for a given Enum + * value, whether it be through separate factory instances or through multiple + * calls to the same factory, attempting to acquire multiple locks with the + * same Enum value (within the same thread) will result in an + * IllegalStateException regardless of the factory's policy. For example: + * + *

       {@code
    +   * CycleDetectingLockFactory.WithExplicitOrdering factory1 =
    +   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...);
    +   * CycleDetectingLockFactory.WithExplicitOrdering factory2 =
    +   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...);
    +   *
    +   * Lock lockA = factory1.newReentrantLock(MyLockOrder.FIRST);
    +   * Lock lockB = factory1.newReentrantLock(MyLockOrder.FIRST);
    +   * Lock lockC = factory2.newReentrantLock(MyLockOrder.FIRST);
    +   *
    +   * lockA.lock();
    +   *
    +   * lockB.lock();  // will throw an IllegalStateException
    +   * lockC.lock();  // will throw an IllegalStateException
    +   *
    +   * lockA.lock();  // reentrant acquisition is okay
    +   * }
    + * + * It is the responsibility of the application to ensure that multiple lock + * instances with the same rank are never acquired in the same thread. + * + * @param The Enum type representing the explicit lock ordering. + * @since 13.0 + */ + @Beta + public static final class WithExplicitOrdering> + extends CycleDetectingLockFactory { + + private final Map lockGraphNodes; + + @VisibleForTesting + WithExplicitOrdering( + Policy policy, Map lockGraphNodes) { + super(policy); + this.lockGraphNodes = lockGraphNodes; + } + + /** + * Equivalent to {@code newReentrantLock(rank, false)}. + */ + public ReentrantLock newReentrantLock(E rank) { + return newReentrantLock(rank, false); + } + + /** + * Creates a {@link ReentrantLock} with the given fairness policy and rank. + * The values returned by {@link Enum#getDeclaringClass()} and + * {@link Enum#name()} are used to describe the lock in warning or + * exception output. + * + * @throws IllegalStateException If the factory has already created a + * {@code Lock} with the specified rank. + */ + public ReentrantLock newReentrantLock(E rank, boolean fair) { + return policy == Policies.DISABLED ? new ReentrantLock(fair) + : new CycleDetectingReentrantLock(lockGraphNodes.get(rank), fair); + } + + /** + * Equivalent to {@code newReentrantReadWriteLock(rank, false)}. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock(E rank) { + return newReentrantReadWriteLock(rank, false); + } + + /** + * Creates a {@link ReentrantReadWriteLock} with the given fairness policy + * and rank. The values returned by {@link Enum#getDeclaringClass()} and + * {@link Enum#name()} are used to describe the lock in warning or exception + * output. + * + * @throws IllegalStateException If the factory has already created a + * {@code Lock} with the specified rank. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock( + E rank, boolean fair) { + return policy == Policies.DISABLED ? new ReentrantReadWriteLock(fair) + : new CycleDetectingReentrantReadWriteLock( + lockGraphNodes.get(rank), fair); + } + } + + /** + * For a given Enum type, creates an immutable map from each of the Enum's + * values to a corresponding LockGraphNode, with the + * {@code allowedPriorLocks} and {@code disallowedPriorLocks} prepopulated + * with nodes according to the natural ordering of the associated Enum values. + */ + @VisibleForTesting + static class OrderedLockGraphNodesCreator + implements Function, + Map> { + + @Override + @SuppressWarnings("unchecked") // There's no way to properly express with + // wildcards the recursive Enum type required by createNodesFor(), and the + // Map/Function types must use wildcards since they accept any Enum class. + public Map apply( + Class clazz) { + return createNodesFor(clazz); + } + + > Map createNodesFor(Class clazz) { + EnumMap map = Maps.newEnumMap(clazz); + E[] keys = clazz.getEnumConstants(); + final int numKeys = keys.length; + ArrayList nodes = + Lists.newArrayListWithCapacity(numKeys); + // Create a LockGraphNode for each enum value. + for (E key : keys) { + LockGraphNode node = new LockGraphNode(getLockName(key)); + nodes.add(node); + map.put(key, node); + } + // Pre-populate all allowedPriorLocks with nodes of smaller ordinal. + for (int i = 1; i < numKeys; i++) { + nodes.get(i).checkAcquiredLocks(Policies.THROW, nodes.subList(0, i)); + } + // Pre-populate all disallowedPriorLocks with nodes of larger ordinal. + for (int i = 0; i < numKeys - 1; i++) { + nodes.get(i).checkAcquiredLocks( + Policies.DISABLED, nodes.subList(i + 1, numKeys)); + } + return Collections.unmodifiableMap(map); + } + + /** + * For the given Enum value {@code rank}, returns the value's + * {@code "EnumClass.name"}, which is used in exception and warning + * output. + */ + private String getLockName(Enum rank) { + return rank.getDeclaringClass().getSimpleName() + "." + rank.name(); + } + } + + //////// Implementation ///////// + + private static final Logger logger = Logger.getLogger( + CycleDetectingLockFactory.class.getName()); + + final Policy policy; + + private CycleDetectingLockFactory(Policy policy) { + this.policy = policy; + } + + /** + * Tracks the currently acquired locks for each Thread, kept up to date by + * calls to {@link #aboutToAcquire(CycleDetectingLock)} and + * {@link #lockStateChanged(CycleDetectingLock)}. + */ + // This is logically a Set, but an ArrayList is used to minimize the amount + // of allocation done on lock()/unlock(). + private static final ThreadLocal> + acquiredLocks = new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return Lists.newArrayListWithCapacity(3); + } + }; + + /** + * A Throwable used to record a stack trace that illustrates an example of + * a specific lock acquisition ordering. The top of the stack trace is + * truncated such that it starts with the acquisition of the lock in + * question, e.g. + * + *
    +   * com...ExampleStackTrace: LockB -> LockC
    +   *   at com...CycleDetectingReentrantLock.lock(CycleDetectingLockFactory.java:443)
    +   *   at ...
    +   *   at ...
    +   *   at com...MyClass.someMethodThatAcquiresLockB(MyClass.java:123)
    +   * 
    + */ + private static class ExampleStackTrace extends IllegalStateException { + + static final StackTraceElement[] EMPTY_STACK_TRACE = + new StackTraceElement[0]; + + static Set EXCLUDED_CLASS_NAMES = ImmutableSet.of( + CycleDetectingLockFactory.class.getName(), + ExampleStackTrace.class.getName(), + LockGraphNode.class.getName()); + + ExampleStackTrace(LockGraphNode node1, LockGraphNode node2) { + super(node1.getLockName() + " -> " + node2.getLockName()); + StackTraceElement[] origStackTrace = getStackTrace(); + for (int i = 0, n = origStackTrace.length; i < n; i++) { + if (WithExplicitOrdering.class.getName().equals( + origStackTrace[i].getClassName())) { + // For pre-populated disallowedPriorLocks edges, omit the stack trace. + setStackTrace(EMPTY_STACK_TRACE); + break; + } + if (!EXCLUDED_CLASS_NAMES.contains(origStackTrace[i].getClassName())) { + setStackTrace(Arrays.copyOfRange(origStackTrace, i, n)); + break; + } + } + } + } + + /** + * Represents a detected cycle in lock acquisition ordering. The exception + * includes a causal chain of {@code ExampleStackTrace}s to illustrate the + * cycle, e.g. + * + *
    +   * com....PotentialDeadlockException: Potential Deadlock from LockC -> ReadWriteA
    +   *   at ...
    +   *   at ...
    +   * Caused by: com...ExampleStackTrace: LockB -> LockC
    +   *   at ...
    +   *   at ...
    +   * Caused by: com...ExampleStackTrace: ReadWriteA -> LockB
    +   *   at ...
    +   *   at ...
    +   * 
    + * + * Instances are logged for the {@code Policies.WARN}, and thrown for + * {@code Policies.THROW}. + * + * @since 13.0 + */ + @Beta + public static final class PotentialDeadlockException + extends ExampleStackTrace { + + private final ExampleStackTrace conflictingStackTrace; + + private PotentialDeadlockException( + LockGraphNode node1, + LockGraphNode node2, + ExampleStackTrace conflictingStackTrace) { + super(node1, node2); + this.conflictingStackTrace = conflictingStackTrace; + initCause(conflictingStackTrace); + } + + public ExampleStackTrace getConflictingStackTrace() { + return conflictingStackTrace; + } + + /** + * Appends the chain of messages from the {@code conflictingStackTrace} to + * the original {@code message}. + */ + @Override + public String getMessage() { + StringBuilder message = new StringBuilder(super.getMessage()); + for (Throwable t = conflictingStackTrace; t != null; t = t.getCause()) { + message.append(", ").append(t.getMessage()); + } + return message.toString(); + } + } + + /** + * Internal Lock implementations implement the {@code CycleDetectingLock} + * interface, allowing the detection logic to treat all locks in the same + * manner. + */ + private interface CycleDetectingLock { + + /** @return the {@link LockGraphNode} associated with this lock. */ + LockGraphNode getLockGraphNode(); + + /** @return {@code true} if the current thread has acquired this lock. */ + boolean isAcquiredByCurrentThread(); + } + + /** + * A {@code LockGraphNode} associated with each lock instance keeps track of + * the directed edges in the lock acquisition graph. + */ + private static class LockGraphNode { + + /** + * The map tracking the locks that are known to be acquired before this + * lock, each associated with an example stack trace. Locks are weakly keyed + * to allow proper garbage collection when they are no longer referenced. + */ + final Map allowedPriorLocks = + new MapMaker().weakKeys().makeMap(); + + /** + * The map tracking lock nodes that can cause a lock acquisition cycle if + * acquired before this node. + */ + final Map + disallowedPriorLocks = new MapMaker().weakKeys().makeMap(); + + final String lockName; + + LockGraphNode(String lockName) { + this.lockName = Preconditions.checkNotNull(lockName); + } + + String getLockName() { + return lockName; + } + + void checkAcquiredLocks( + Policy policy, List acquiredLocks) { + for (int i = 0, size = acquiredLocks.size(); i < size; i++) { + checkAcquiredLock(policy, acquiredLocks.get(i)); + } + } + + /** + * Checks the acquisition-ordering between {@code this}, which is about to + * be acquired, and the specified {@code acquiredLock}. + *

    + * When this method returns, the {@code acquiredLock} should be in either + * the {@code preAcquireLocks} map, for the case in which it is safe to + * acquire {@code this} after the {@code acquiredLock}, or in the + * {@code disallowedPriorLocks} map, in which case it is not safe. + */ + void checkAcquiredLock(Policy policy, LockGraphNode acquiredLock) { + // checkAcquiredLock() should never be invoked by a lock that has already + // been acquired. For unordered locks, aboutToAcquire() ensures this by + // checking isAcquiredByCurrentThread(). For ordered locks, however, this + // can happen because multiple locks may share the same LockGraphNode. In + // this situation, throw an IllegalStateException as defined by contract + // described in the documentation of WithExplicitOrdering. + Preconditions.checkState( + this != acquiredLock, + "Attempted to acquire multiple locks with the same rank " + + acquiredLock.getLockName()); + + if (allowedPriorLocks.containsKey(acquiredLock)) { + // The acquisition ordering from "acquiredLock" to "this" has already + // been verified as safe. In a properly written application, this is + // the common case. + return; + } + PotentialDeadlockException previousDeadlockException = + disallowedPriorLocks.get(acquiredLock); + if (previousDeadlockException != null) { + // Previously determined to be an unsafe lock acquisition. + // Create a new PotentialDeadlockException with the same causal chain + // (the example cycle) as that of the cached exception. + PotentialDeadlockException exception = new PotentialDeadlockException( + acquiredLock, this, + previousDeadlockException.getConflictingStackTrace()); + policy.handlePotentialDeadlock(exception); + return; + } + // Otherwise, it's the first time seeing this lock relationship. Look for + // a path from the acquiredLock to this. + Set seen = Sets.newIdentityHashSet(); + ExampleStackTrace path = acquiredLock.findPathTo(this, seen); + + if (path == null) { + // this can be safely acquired after the acquiredLock. + // + // Note that there is a race condition here which can result in missing + // a cyclic edge: it's possible for two threads to simultaneous find + // "safe" edges which together form a cycle. Preventing this race + // condition efficiently without _introducing_ deadlock is probably + // tricky. For now, just accept the race condition---missing a warning + // now and then is still better than having no deadlock detection. + allowedPriorLocks.put( + acquiredLock, new ExampleStackTrace(acquiredLock, this)); + } else { + // Unsafe acquisition order detected. Create and cache a + // PotentialDeadlockException. + PotentialDeadlockException exception = + new PotentialDeadlockException(acquiredLock, this, path); + disallowedPriorLocks.put(acquiredLock, exception); + policy.handlePotentialDeadlock(exception); + } + } + + /** + * Performs a depth-first traversal of the graph edges defined by each + * node's {@code allowedPriorLocks} to find a path between {@code this} and + * the specified {@code lock}. + * + * @return If a path was found, a chained {@link ExampleStackTrace} + * illustrating the path to the {@code lock}, or {@code null} if no path + * was found. + */ + @Nullable + private ExampleStackTrace findPathTo( + LockGraphNode node, Set seen) { + if (!seen.add(this)) { + return null; // Already traversed this node. + } + ExampleStackTrace found = allowedPriorLocks.get(node); + if (found != null) { + return found; // Found a path ending at the node! + } + // Recurse the edges. + for (Map.Entry entry : + allowedPriorLocks.entrySet()) { + LockGraphNode preAcquiredLock = entry.getKey(); + found = preAcquiredLock.findPathTo(node, seen); + if (found != null) { + // One of this node's allowedPriorLocks found a path. Prepend an + // ExampleStackTrace(preAcquiredLock, this) to the returned chain of + // ExampleStackTraces. + ExampleStackTrace path = + new ExampleStackTrace(preAcquiredLock, this); + path.setStackTrace(entry.getValue().getStackTrace()); + path.initCause(found); + return path; + } + } + return null; + } + } + + /** + * CycleDetectingLock implementations must call this method before attempting + * to acquire the lock. + */ + private void aboutToAcquire(CycleDetectingLock lock) { + if (!lock.isAcquiredByCurrentThread()) { + ArrayList acquiredLockList = acquiredLocks.get(); + LockGraphNode node = lock.getLockGraphNode(); + node.checkAcquiredLocks(policy, acquiredLockList); + acquiredLockList.add(node); + } + } + + /** + * CycleDetectingLock implementations must call this method in a + * {@code finally} clause after any attempt to change the lock state, + * including both lock and unlock attempts. Failure to do so can result in + * corrupting the acquireLocks set. + */ + private void lockStateChanged(CycleDetectingLock lock) { + if (!lock.isAcquiredByCurrentThread()) { + ArrayList acquiredLockList = acquiredLocks.get(); + LockGraphNode node = lock.getLockGraphNode(); + // Iterate in reverse because locks are usually locked/unlocked in a + // LIFO order. + for (int i = acquiredLockList.size() - 1; i >=0; i--) { + if (acquiredLockList.get(i) == node) { + acquiredLockList.remove(i); + break; + } + } + } + } + + final class CycleDetectingReentrantLock + extends ReentrantLock implements CycleDetectingLock { + + private final LockGraphNode lockGraphNode; + + private CycleDetectingReentrantLock( + LockGraphNode lockGraphNode, boolean fair) { + super(fair); + this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); + } + + ///// CycleDetectingLock methods. ///// + + @Override + public LockGraphNode getLockGraphNode() { + return lockGraphNode; + } + + @Override + public boolean isAcquiredByCurrentThread() { + return isHeldByCurrentThread(); + } + + ///// Overridden ReentrantLock methods. ///// + + @Override + public void lock() { + aboutToAcquire(this); + try { + super.lock(); + } finally { + lockStateChanged(this); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(this); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(this); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(this); + try { + return super.tryLock(); + } finally { + lockStateChanged(this); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + aboutToAcquire(this); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(this); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(this); + } + } + } + + final class CycleDetectingReentrantReadWriteLock + extends ReentrantReadWriteLock implements CycleDetectingLock { + + // These ReadLock/WriteLock implementations shadow those in the + // ReentrantReadWriteLock superclass. They are simply wrappers around the + // internal Sync object, so this is safe since the shadowed locks are never + // exposed or used. + private final CycleDetectingReentrantReadLock readLock; + private final CycleDetectingReentrantWriteLock writeLock; + + private final LockGraphNode lockGraphNode; + + private CycleDetectingReentrantReadWriteLock( + LockGraphNode lockGraphNode, boolean fair) { + super(fair); + this.readLock = new CycleDetectingReentrantReadLock(this); + this.writeLock = new CycleDetectingReentrantWriteLock(this); + this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); + } + + ///// Overridden ReentrantReadWriteLock methods. ///// + + @Override + public ReadLock readLock() { + return readLock; + } + + @Override + public WriteLock writeLock() { + return writeLock; + } + + ///// CycleDetectingLock methods. ///// + + @Override + public LockGraphNode getLockGraphNode() { + return lockGraphNode; + } + + @Override + public boolean isAcquiredByCurrentThread() { + return isWriteLockedByCurrentThread() || getReadHoldCount() > 0; + } + } + + private class CycleDetectingReentrantReadLock + extends ReentrantReadWriteLock.ReadLock { + + final CycleDetectingReentrantReadWriteLock readWriteLock; + + CycleDetectingReentrantReadLock( + CycleDetectingReentrantReadWriteLock readWriteLock) { + super(readWriteLock); + this.readWriteLock = readWriteLock; + } + + @Override + public void lock() { + aboutToAcquire(readWriteLock); + try { + super.lock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(readWriteLock); + } + } + } + + private class CycleDetectingReentrantWriteLock + extends ReentrantReadWriteLock.WriteLock { + + final CycleDetectingReentrantReadWriteLock readWriteLock; + + CycleDetectingReentrantWriteLock( + CycleDetectingReentrantReadWriteLock readWriteLock) { + super(readWriteLock); + this.readWriteLock = readWriteLock; + } + + @Override + public void lock() { + aboutToAcquire(readWriteLock); + try { + super.lock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(readWriteLock); + } + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/ExecutionError.java b/guava/src/com/google/common/util/concurrent/ExecutionError.java new file mode 100644 index 0000000..1a2edac --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ExecutionError.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * {@link Error} variant of {@link java.util.concurrent.ExecutionException}. As + * with {@code ExecutionException}, the error's {@linkplain #getCause() cause} + * comes from a failed task, possibly run in another thread. That cause should + * itself be an {@code Error}; if not, use {@code ExecutionException} or {@link + * UncheckedExecutionException}. This allows the client code to continue to + * distinguish between exceptions and errors, even when they come from other + * threads. + * + * @author Chris Povirk + * @since 10.0 + */ +@Beta +@GwtCompatible +public class ExecutionError extends Error { + /** + * Creates a new instance with {@code null} as its detail message. + */ + protected ExecutionError() {} + + /** + * Creates a new instance with the given detail message. + */ + protected ExecutionError(String message) { + super(message); + } + + /** + * Creates a new instance with the given detail message and cause. + */ + public ExecutionError(String message, Error cause) { + super(message, cause); + } + + /** + * Creates a new instance with the given cause. + */ + public ExecutionError(Error cause) { + super(cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/util/concurrent/ExecutionList.java b/guava/src/com/google/common/util/concurrent/ExecutionList.java new file mode 100644 index 0000000..d1b78f5 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ExecutionList.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

    A list of listeners, each with an associated {@code Executor}, that + * guarantees that every {@code Runnable} that is {@linkplain #add added} will + * be executed after {@link #execute()} is called. Any {@code Runnable} added + * after the call to {@code execute} is still guaranteed to execute. There is no + * guarantee, however, that listeners will be executed in the order that they + * are added. + * + *

    Exceptions thrown by a listener will be propagated up to the executor. + * Any exception thrown during {@code Executor.execute} (e.g., a {@code + * RejectedExecutionException} or an exception thrown by {@linkplain + * MoreExecutors#sameThreadExecutor inline execution}) will be caught and + * logged. + * + * @author Nishant Thakkar + * @author Sven Mawson + * @since 1.0 + */ +public final class ExecutionList { + + // Logger to log exceptions caught when running runnables. + private static final Logger log = + Logger.getLogger(ExecutionList.class.getName()); + + // The runnable,executor pairs to execute. + private final Queue runnables = Lists.newLinkedList(); + + // Boolean we use mark when execution has started. Only accessed from within + // synchronized blocks. + private boolean executed = false; + + /** Creates a new, empty {@link ExecutionList}. */ + public ExecutionList() { + } + + /** + * Adds the {@code Runnable} and accompanying {@code Executor} to the list of + * listeners to execute. If execution has already begun, the listener is + * executed immediately. + * + *

    Note: For fast, lightweight listeners that would be safe to execute in + * any thread, consider {@link MoreExecutors#sameThreadExecutor}. For heavier + * listeners, {@code sameThreadExecutor()} carries some caveats: First, the + * thread that the listener runs in depends on whether the {@code + * ExecutionList} has been executed at the time it is added. In particular, + * listeners may run in the thread that calls {@code add}. Second, the thread + * that calls {@link #execute} may be an internal implementation thread, such + * as an RPC network thread, and {@code sameThreadExecutor()} listeners may + * run in this thread. Finally, during the execution of a {@code + * sameThreadExecutor} listener, all other registered but unexecuted + * listeners are prevented from running, even if those listeners are to run + * in other executors. + */ + public void add(Runnable runnable, Executor executor) { + // Fail fast on a null. We throw NPE here because the contract of + // Executor states that it throws NPE on null listener, so we propagate + // that contract up into the add method as well. + Preconditions.checkNotNull(runnable, "Runnable was null."); + Preconditions.checkNotNull(executor, "Executor was null."); + + boolean executeImmediate = false; + + // Lock while we check state. We must maintain the lock while adding the + // new pair so that another thread can't run the list out from under us. + // We only add to the list if we have not yet started execution. + synchronized (runnables) { + if (!executed) { + runnables.add(new RunnableExecutorPair(runnable, executor)); + } else { + executeImmediate = true; + } + } + + // Execute the runnable immediately. Because of scheduling this may end up + // getting called before some of the previously added runnables, but we're + // OK with that. If we want to change the contract to guarantee ordering + // among runnables we'd have to modify the logic here to allow it. + if (executeImmediate) { + new RunnableExecutorPair(runnable, executor).execute(); + } + } + + /** + * Runs this execution list, executing all existing pairs in the order they + * were added. However, note that listeners added after this point may be + * executed before those previously added, and note that the execution order + * of all listeners is ultimately chosen by the implementations of the + * supplied executors. + * + *

    This method is idempotent. Calling it several times in parallel is + * semantically equivalent to calling it exactly once. + * + * @since 10.0 (present in 1.0 as {@code run}) + */ + public void execute() { + // Lock while we update our state so the add method above will finish adding + // any listeners before we start to run them. + synchronized (runnables) { + if (executed) { + return; + } + executed = true; + } + + // At this point the runnables will never be modified by another + // thread, so we are safe using it outside of the synchronized block. + while (!runnables.isEmpty()) { + runnables.poll().execute(); + } + } + + private static class RunnableExecutorPair { + final Runnable runnable; + final Executor executor; + + RunnableExecutorPair(Runnable runnable, Executor executor) { + this.runnable = runnable; + this.executor = executor; + } + + void execute() { + try { + executor.execute(runnable); + } catch (RuntimeException e) { + // Log it and keep going, bad runnable and/or executor. Don't + // punish the other runnables if we're given a bad one. We only + // catch RuntimeException because we want Errors to propagate up. + log.log(Level.SEVERE, "RuntimeException while executing runnable " + + runnable + " with executor " + executor, e); + } + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/FakeTimeLimiter.java b/guava/src/com/google/common/util/concurrent/FakeTimeLimiter.java new file mode 100644 index 0000000..890479d --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/FakeTimeLimiter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * A TimeLimiter implementation which actually does not attempt to limit time + * at all. This may be desirable to use in some unit tests. More importantly, + * attempting to debug a call which is time-limited would be extremely annoying, + * so this gives you a time-limiter you can easily swap in for your real + * time-limiter while you're debugging. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@Beta +public final class FakeTimeLimiter implements TimeLimiter { + @Override + public T newProxy(T target, Class interfaceType, long timeoutDuration, + TimeUnit timeoutUnit) { + return target; // ha ha + } + + @Override + public T callWithTimeout(Callable callable, long timeoutDuration, + TimeUnit timeoutUnit, boolean amInterruptible) throws Exception { + return callable.call(); // fooled you + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java b/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java new file mode 100644 index 0000000..f7257a2 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.collect.ForwardingQueue; + +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * A {@link BlockingQueue} which forwards all its method calls to another + * {@link BlockingQueue}. Subclasses should override one or more methods to + * modify the behavior of the backing collection as desired per the decorator pattern. + * + * @author Raimundo Mirisola + * + * @param the type of elements held in this collection + * @since 4.0 + */ +public abstract class ForwardingBlockingQueue extends ForwardingQueue + implements BlockingQueue { + + /** Constructor for use by subclasses. */ + protected ForwardingBlockingQueue() {} + + @Override protected abstract BlockingQueue delegate(); + + @Override public int drainTo( + Collection c, int maxElements) { + return delegate().drainTo(c, maxElements); + } + + @Override public int drainTo(Collection c) { + return delegate().drainTo(c); + } + + @Override public boolean offer(E e, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate().offer(e, timeout, unit); + } + + @Override public E poll(long timeout, TimeUnit unit) + throws InterruptedException { + return delegate().poll(timeout, unit); + } + + @Override public void put(E e) throws InterruptedException { + delegate().put(e); + } + + @Override public int remainingCapacity() { + return delegate().remainingCapacity(); + } + + @Override public E take() throws InterruptedException { + return delegate().take(); + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingCheckedFuture.java b/guava/src/com/google/common/util/concurrent/ForwardingCheckedFuture.java new file mode 100644 index 0000000..1dee47a --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingCheckedFuture.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A future which forwards all its method calls to another future. Subclasses + * should override one or more methods to modify the behavior of the backing + * future as desired per the decorator pattern. + * + *

    Most subclasses can simply extend {@link SimpleForwardingCheckedFuture}. + * + * @param The result type returned by this Future's {@code get} method + * @param The type of the Exception thrown by the Future's + * {@code checkedGet} method + * + * @author Anthony Zana + * @since 9.0 + */ +@Beta +public abstract class ForwardingCheckedFuture + extends ForwardingListenableFuture implements CheckedFuture { + + @Override + public V checkedGet() throws X { + return delegate().checkedGet(); + } + + @Override + public V checkedGet(long timeout, TimeUnit unit) throws TimeoutException, X { + return delegate().checkedGet(timeout, unit); + } + + @Override + protected abstract CheckedFuture delegate(); + + // TODO(cpovirk): Use Standard Javadoc form for SimpleForwarding* + /** + * A simplified version of {@link ForwardingCheckedFuture} where subclasses + * can pass in an already constructed {@link CheckedFuture} as the delegate. + * + * @since 9.0 + */ + @Beta + public abstract static class SimpleForwardingCheckedFuture< + V, X extends Exception> extends ForwardingCheckedFuture { + private final CheckedFuture delegate; + + protected SimpleForwardingCheckedFuture(CheckedFuture delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final CheckedFuture delegate() { + return delegate; + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java b/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java new file mode 100644 index 0000000..9c09af0 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.collect.ForwardingObject; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * An executor service which forwards all its method calls to another executor + * service. Subclasses should override one or more methods to modify the + * behavior of the backing executor service as desired per the decorator pattern. + * + * @author Kurt Alfred Kluever + * @since 10.0 + */ +public abstract class ForwardingExecutorService extends ForwardingObject + implements ExecutorService { + /** Constructor for use by subclasses. */ + protected ForwardingExecutorService() {} + + @Override + protected abstract ExecutorService delegate(); + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return delegate().awaitTermination(timeout, unit); + } + + @Override + public List> invokeAll( + Collection> tasks) throws InterruptedException { + return delegate().invokeAll(tasks); + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate().invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + return delegate().invokeAny(tasks); + } + + @Override + public T invokeAny( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate().invokeAny(tasks, timeout, unit); + } + + @Override + public boolean isShutdown() { + return delegate().isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate().isTerminated(); + } + + @Override + public void shutdown() { + delegate().shutdown(); + } + + @Override + public List shutdownNow() { + return delegate().shutdownNow(); + } + + @Override + public void execute(Runnable command) { + delegate().execute(command); + } + + public Future submit(Callable task) { + return delegate().submit(task); + } + + @Override + public Future submit(Runnable task) { + return delegate().submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return delegate().submit(task, result); + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingFuture.java b/guava/src/com/google/common/util/concurrent/ForwardingFuture.java new file mode 100644 index 0000000..9fcd119 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingFuture.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingObject; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A {@link Future} which forwards all its method calls to another future. + * Subclasses should override one or more methods to modify the behavior of + * the backing future as desired per the decorator pattern. + * + *

    Most subclasses can just use {@link SimpleForwardingFuture}. + * + * @author Sven Mawson + * @since 1.0 + */ +public abstract class ForwardingFuture extends ForwardingObject + implements Future { + + /** Constructor for use by subclasses. */ + protected ForwardingFuture() {} + + @Override protected abstract Future delegate(); + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return delegate().cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return delegate().isCancelled(); + } + + @Override + public boolean isDone() { + return delegate().isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return delegate().get(); + } + + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate().get(timeout, unit); + } + + /* + * TODO(cpovirk): Use standard Javadoc form for SimpleForwarding* class and + * constructor + */ + /** + * A simplified version of {@link ForwardingFuture} where subclasses + * can pass in an already constructed {@link Future} as the delegate. + * + * @since 9.0 + */ + public abstract static class SimpleForwardingFuture + extends ForwardingFuture { + private final Future delegate; + + protected SimpleForwardingFuture(Future delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final Future delegate() { + return delegate; + } + + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java b/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java new file mode 100644 index 0000000..79f6d53 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.base.Preconditions; + +import java.util.concurrent.Executor; + +/** + * A {@link ListenableFuture} which forwards all its method calls to another + * future. Subclasses should override one or more methods to modify the behavior + * of the backing future as desired per the decorator pattern. + * + *

    Most subclasses can just use {@link SimpleForwardingListenableFuture}. + * + * @param The result type returned by this Future's {@code get} method + * + * @author Shardul Deo + * @since 4.0 + */ +public abstract class ForwardingListenableFuture extends ForwardingFuture + implements ListenableFuture { + + /** Constructor for use by subclasses. */ + protected ForwardingListenableFuture() {} + + @Override + protected abstract ListenableFuture delegate(); + + @Override + public void addListener(Runnable listener, Executor exec) { + delegate().addListener(listener, exec); + } + + /* + * TODO(cpovirk): Use standard Javadoc form for SimpleForwarding* class and + * constructor + */ + /** + * A simplified version of {@link ForwardingListenableFuture} where subclasses + * can pass in an already constructed {@link ListenableFuture} + * as the delegate. + * + * @since 9.0 + */ + public abstract static class SimpleForwardingListenableFuture + extends ForwardingListenableFuture { + private final ListenableFuture delegate; + + protected SimpleForwardingListenableFuture(ListenableFuture delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final ListenableFuture delegate() { + return delegate; + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java b/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java new file mode 100644 index 0000000..27e44de --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.Callable; + +/** + * A listening executor service which forwards all its method calls to another + * listening executor service. Subclasses should override one or more methods to + * modify the behavior of the backing executor service as desired per the decorator pattern. + * + * @author Isaac Shum + * @since 10.0 + */ +public abstract class ForwardingListeningExecutorService + extends ForwardingExecutorService implements ListeningExecutorService { + /** Constructor for use by subclasses. */ + protected ForwardingListeningExecutorService() {} + + @Override + protected abstract ListeningExecutorService delegate(); + + @Override + public ListenableFuture submit(Callable task) { + return delegate().submit(task); + } + + @Override + public ListenableFuture submit(Runnable task) { + return delegate().submit(task); + } + + @Override + public ListenableFuture submit(Runnable task, T result) { + return delegate().submit(task, result); + } +} diff --git a/guava/src/com/google/common/util/concurrent/ForwardingService.java b/guava/src/com/google/common/util/concurrent/ForwardingService.java new file mode 100644 index 0000000..0fff279 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ForwardingService.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ForwardingObject; + +import java.util.concurrent.Executor; + +/** + * A {@link Service} that forwards all method calls to another service. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public abstract class ForwardingService extends ForwardingObject + implements Service { + + /** Constructor for use by subclasses. */ + protected ForwardingService() {} + + @Override protected abstract Service delegate(); + + @Override public ListenableFuture start() { + return delegate().start(); + } + + @Override public State state() { + return delegate().state(); + } + + @Override public ListenableFuture stop() { + return delegate().stop(); + } + + @Override public State startAndWait() { + return delegate().startAndWait(); + } + + @Override public State stopAndWait() { + return delegate().stopAndWait(); + } + + @Override public boolean isRunning() { + return delegate().isRunning(); + } + + @Override public void addListener(Listener listener, Executor executor) { + delegate().addListener(listener, executor); + } + + /** + * A sensible default implementation of {@link #startAndWait()}, in terms of + * {@link #start}. If you override {@link #start}, you may wish to override + * {@link #startAndWait()} to forward to this implementation. + * @since 9.0 + */ + protected State standardStartAndWait() { + return Futures.getUnchecked(start()); + } + + /** + * A sensible default implementation of {@link #stopAndWait()}, in terms of + * {@link #stop}. If you override {@link #stop}, you may wish to override + * {@link #stopAndWait()} to forward to this implementation. + * @since 9.0 + */ + protected State standardStopAndWait() { + return Futures.getUnchecked(stop()); + } +} diff --git a/guava/src/com/google/common/util/concurrent/FutureCallback.java b/guava/src/com/google/common/util/concurrent/FutureCallback.java new file mode 100644 index 0000000..7b39d4a --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/FutureCallback.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +/** + * A callback for accepting the results of a {@link java.util.concurrent.Future} + * computation asynchronously. + * + *

    To attach to a {@link ListenableFuture} use {@link Futures#addCallback}. + * + * @author Anthony Zana + * @since 10.0 + */ +@Beta +public interface FutureCallback { + /** + * Invoked with the result of the {@code Future} computation when it is + * successful. + */ + void onSuccess(V result); + + /** + * Invoked when a {@code Future} computation fails or is canceled. + * + *

    If the future's {@link Future#get() get} method throws an {@link + * ExecutionException}, then the cause is passed to this method. Any other + * thrown object is passed unaltered. + */ + void onFailure(Throwable t); +} diff --git a/guava/src/com/google/common/util/concurrent/Futures.java b/guava/src/com/google/common/util/concurrent/Futures.java new file mode 100644 index 0000000..0ff8e8a --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Futures.java @@ -0,0 +1,1249 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static com.google.common.util.concurrent.Uninterruptibles.putUninterruptibly; +import static com.google.common.util.concurrent.Uninterruptibles.takeUninterruptibly; +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to the {@link Future} interface. + * + *

    Many of these methods use the {@link ListenableFuture} API; consult the + * Guava User Guide article on + * {@code ListenableFuture}. + * + * @author Kevin Bourrillion + * @author Nishant Thakkar + * @author Sven Mawson + * @since 1.0 + */ +@Beta +public final class Futures { + private Futures() {} + + /** + * Creates a {@link CheckedFuture} out of a normal {@link ListenableFuture} + * and a {@link Function} that maps from {@link Exception} instances into the + * appropriate checked type. + * + *

    The given mapping function will be applied to an + * {@link InterruptedException}, a {@link CancellationException}, or an + * {@link ExecutionException} with the actual cause of the exception. + * See {@link Future#get()} for details on the exceptions thrown. + * + * @since 9.0 (source-compatible since 1.0) + */ + public static CheckedFuture makeChecked( + ListenableFuture future, Function mapper) { + return new MappingCheckedFuture(checkNotNull(future), mapper); + } + + /** + * Creates a {@code ListenableFuture} which has its value set immediately upon + * construction. The getters just return the value. This {@code Future} can't + * be canceled or timed out and its {@code isDone()} method always returns + * {@code true}. + */ + public static ListenableFuture immediateFuture(@Nullable V value) { + SettableFuture future = SettableFuture.create(); + future.set(value); + return future; + } + + /** + * Returns a {@code CheckedFuture} which has its value set immediately upon + * construction. + * + *

    The returned {@code Future} can't be cancelled, and its {@code isDone()} + * method always returns {@code true}. Calling {@code get()} or {@code + * checkedGet()} will immediately return the provided value. + */ + public static CheckedFuture + immediateCheckedFuture(@Nullable V value) { + SettableFuture future = SettableFuture.create(); + future.set(value); + return Futures.makeChecked(future, new Function() { + @Override + public X apply(Exception e) { + throw new AssertionError("impossible"); + } + }); + } + + /** + * Returns a {@code ListenableFuture} which has an exception set immediately + * upon construction. + * + *

    The returned {@code Future} can't be cancelled, and its {@code isDone()} + * method always returns {@code true}. Calling {@code get()} will immediately + * throw the provided {@code Throwable} wrapped in an {@code + * ExecutionException}. + * + * @throws Error if the throwable is an {@link Error}. + */ + public static ListenableFuture immediateFailedFuture( + Throwable throwable) { + checkNotNull(throwable); + SettableFuture future = SettableFuture.create(); + future.setException(throwable); + return future; + } + + /** + * Returns a {@code CheckedFuture} which has an exception set immediately upon + * construction. + * + *

    The returned {@code Future} can't be cancelled, and its {@code isDone()} + * method always returns {@code true}. Calling {@code get()} will immediately + * throw the provided {@code Throwable} wrapped in an {@code + * ExecutionException}, and calling {@code checkedGet()} will throw the + * provided exception itself. + * + * @throws Error if the throwable is an {@link Error}. + */ + public static CheckedFuture + immediateFailedCheckedFuture(final X exception) { + checkNotNull(exception); + return makeChecked(Futures.immediateFailedFuture(exception), + new Function() { + @Override + public X apply(Exception e) { + return exception; + } + }); + } + + /** + * Returns a new {@code ListenableFuture} whose result is asynchronously + * derived from the result of the given {@code Future}. More precisely, the + * returned {@code Future} takes its result from a {@code Future} produced by + * applying the given {@code AsyncFunction} to the result of the original + * {@code Future}. Example: + * + *

       {@code
    +   *   ListenableFuture rowKeyFuture = indexService.lookUp(query);
    +   *   AsyncFunction queryFunction =
    +   *       new AsyncFunction() {
    +   *         public ListenableFuture apply(RowKey rowKey) {
    +   *           return dataService.read(rowKey);
    +   *         }
    +   *       };
    +   *   ListenableFuture queryFuture =
    +   *       transform(rowKeyFuture, queryFunction);
    +   * }
    + * + * Note: If the derived {@code Future} is slow or heavyweight to create + * (whether the {@code Future} itself is slow or heavyweight to complete is + * irrelevant), consider {@linkplain #transform(ListenableFuture, + * AsyncFunction, Executor) supplying an executor}. If you do not supply an + * executor, {@code transform} will use {@link + * MoreExecutors#sameThreadExecutor sameThreadExecutor}, which carries some + * caveats for heavier operations. For example, the call to {@code + * function.apply} may run on an unpredictable or undesirable thread: + * + *
      + *
    • If the input {@code Future} is done at the time {@code transform} is + * called, {@code transform} will call {@code function.apply} inline. + *
    • If the input {@code Future} is not yet done, {@code transform} will + * schedule {@code function.apply} to be run by the thread that completes the + * input {@code Future}, which may be an internal system thread such as an + * RPC network thread. + *
    + * + * Also note that, regardless of which thread executes {@code + * function.apply}, all other registered but unexecuted listeners are + * prevented from running during its execution, even if those listeners are + * to run in other executors. + * + *

    The returned {@code Future} attempts to keep its cancellation state in + * sync with that of the input future and that of the future returned by the + * function. That is, if the returned {@code Future} is cancelled, it will + * attempt to cancel the other two, and if either of the other two is + * cancelled, the returned {@code Future} will receive a callback in which it + * will attempt to cancel itself. + * + * @param input The future to transform + * @param function A function to transform the result of the input future + * to the result of the output future + * @return A future that holds result of the function (if the input succeeded) + * or the original input's failure (if not) + * @since 11.0 + */ + public static ListenableFuture transform(ListenableFuture input, + AsyncFunction function) { + return transform(input, function, MoreExecutors.sameThreadExecutor()); + } + + /** + * Returns a new {@code ListenableFuture} whose result is asynchronously + * derived from the result of the given {@code Future}. More precisely, the + * returned {@code Future} takes its result from a {@code Future} produced by + * applying the given {@code AsyncFunction} to the result of the original + * {@code Future}. Example: + * + *

       {@code
    +   *   ListenableFuture rowKeyFuture = indexService.lookUp(query);
    +   *   AsyncFunction queryFunction =
    +   *       new AsyncFunction() {
    +   *         public ListenableFuture apply(RowKey rowKey) {
    +   *           return dataService.read(rowKey);
    +   *         }
    +   *       };
    +   *   ListenableFuture queryFuture =
    +   *       transform(rowKeyFuture, queryFunction, executor);
    +   * }
    + * + *

    The returned {@code Future} attempts to keep its cancellation state in + * sync with that of the input future and that of the future returned by the + * chain function. That is, if the returned {@code Future} is cancelled, it + * will attempt to cancel the other two, and if either of the other two is + * cancelled, the returned {@code Future} will receive a callback in which it + * will attempt to cancel itself. + * + *

    When the execution of {@code function.apply} is fast and lightweight + * (though the {@code Future} it returns need not meet these criteria), + * consider {@linkplain #transform(ListenableFuture, AsyncFunction) omitting + * the executor} or explicitly specifying {@code sameThreadExecutor}. + * However, be aware of the caveats documented in the link above. + * + * @param input The future to transform + * @param function A function to transform the result of the input future + * to the result of the output future + * @param executor Executor to run the function in. + * @return A future that holds result of the function (if the input succeeded) + * or the original input's failure (if not) + * @since 11.0 + */ + public static ListenableFuture transform(ListenableFuture input, + AsyncFunction function, + Executor executor) { + ChainingListenableFuture output = + new ChainingListenableFuture(function, input); + input.addListener(output, executor); + return output; + } + + /** + * Returns a new {@code ListenableFuture} whose result is the product of + * applying the given {@code Function} to the result of the given {@code + * Future}. Example: + * + *

       {@code
    +   *   ListenableFuture queryFuture = ...;
    +   *   Function> rowsFunction =
    +   *       new Function>() {
    +   *         public List apply(QueryResult queryResult) {
    +   *           return queryResult.getRows();
    +   *         }
    +   *       };
    +   *   ListenableFuture> rowsFuture =
    +   *       transform(queryFuture, rowsFunction);
    +   * }
    + * + * Note: If the transformation is slow or heavyweight, consider {@linkplain + * #transform(ListenableFuture, Function, Executor) supplying an executor}. + * If you do not supply an executor, {@code transform} will use {@link + * MoreExecutors#sameThreadExecutor sameThreadExecutor}, which carries some + * caveats for heavier operations. For example, the call to {@code + * function.apply} may run on an unpredictable or undesirable thread: + * + *
      + *
    • If the input {@code Future} is done at the time {@code transform} is + * called, {@code transform} will call {@code function.apply} inline. + *
    • If the input {@code Future} is not yet done, {@code transform} will + * schedule {@code function.apply} to be run by the thread that completes the + * input {@code Future}, which may be an internal system thread such as an + * RPC network thread. + *
    + * + * Also note that, regardless of which thread executes {@code + * function.apply}, all other registered but unexecuted listeners are + * prevented from running during its execution, even if those listeners are + * to run in other executors. + * + *

    The returned {@code Future} attempts to keep its cancellation state in + * sync with that of the input future. That is, if the returned {@code Future} + * is cancelled, it will attempt to cancel the input, and if the input is + * cancelled, the returned {@code Future} will receive a callback in which it + * will attempt to cancel itself. + * + *

    An example use of this method is to convert a serializable object + * returned from an RPC into a POJO. + * + * @param input The future to transform + * @param function A Function to transform the results of the provided future + * to the results of the returned future. This will be run in the thread + * that notifies input it is complete. + * @return A future that holds result of the transformation. + * @since 9.0 (in 1.0 as {@code compose}) + */ + public static ListenableFuture transform(ListenableFuture input, + final Function function) { + return transform(input, function, MoreExecutors.sameThreadExecutor()); + } + + /** + * Returns a new {@code ListenableFuture} whose result is the product of + * applying the given {@code Function} to the result of the given {@code + * Future}. Example: + * + *

       {@code
    +   *   ListenableFuture queryFuture = ...;
    +   *   Function> rowsFunction =
    +   *       new Function>() {
    +   *         public List apply(QueryResult queryResult) {
    +   *           return queryResult.getRows();
    +   *         }
    +   *       };
    +   *   ListenableFuture> rowsFuture =
    +   *       transform(queryFuture, rowsFunction, executor);
    +   * }
    + * + *

    The returned {@code Future} attempts to keep its cancellation state in + * sync with that of the input future. That is, if the returned {@code Future} + * is cancelled, it will attempt to cancel the input, and if the input is + * cancelled, the returned {@code Future} will receive a callback in which it + * will attempt to cancel itself. + * + *

    An example use of this method is to convert a serializable object + * returned from an RPC into a POJO. + * + *

    When the transformation is fast and lightweight, consider {@linkplain + * #transform(ListenableFuture, Function) omitting the executor} or + * explicitly specifying {@code sameThreadExecutor}. However, be aware of the + * caveats documented in the link above. + * + * @param input The future to transform + * @param function A Function to transform the results of the provided future + * to the results of the returned future. + * @param executor Executor to run the function in. + * @return A future that holds result of the transformation. + * @since 9.0 (in 2.0 as {@code compose}) + */ + public static ListenableFuture transform(ListenableFuture input, + final Function function, Executor executor) { + checkNotNull(function); + AsyncFunction wrapperFunction + = new AsyncFunction() { + @Override public ListenableFuture apply(I input) { + O output = function.apply(input); + return immediateFuture(output); + } + }; + return transform(input, wrapperFunction, executor); + } + + /** + * Like {@link #transform(ListenableFuture, Function)} except that the + * transformation {@code function} is invoked on each call to + * {@link Future#get() get()} on the returned future. + * + *

    The returned {@code Future} reflects the input's cancellation + * state directly, and any attempt to cancel the returned Future is likewise + * passed through to the input Future. + * + *

    Note that calls to {@linkplain Future#get(long, TimeUnit) timed get} + * only apply the timeout to the execution of the underlying {@code Future}, + * not to the execution of the transformation function. + * + *

    The primary audience of this method is callers of {@code transform} + * who don't have a {@code ListenableFuture} available and + * do not mind repeated, lazy function evaluation. + * + * @param input The future to transform + * @param function A Function to transform the results of the provided future + * to the results of the returned future. + * @return A future that returns the result of the transformation. + * @since 10.0 + */ + @Beta + public static Future lazyTransform(final Future input, + final Function function) { + checkNotNull(input); + checkNotNull(function); + return new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return input.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return input.isCancelled(); + } + + @Override + public boolean isDone() { + return input.isDone(); + } + + @Override + public O get() throws InterruptedException, ExecutionException { + return applyTransformation(input.get()); + } + + @Override + public O get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return applyTransformation(input.get(timeout, unit)); + } + + private O applyTransformation(I input) throws ExecutionException { + try { + return function.apply(input); + } catch (Throwable t) { + throw new ExecutionException(t); + } + } + }; + } + + /** + * An implementation of {@code ListenableFuture} that also implements + * {@code Runnable} so that it can be used to nest ListenableFutures. + * Once the passed-in {@code ListenableFuture} is complete, it calls the + * passed-in {@code Function} to generate the result. + * + *

    If the function throws any checked exceptions, they should be wrapped + * in a {@code UndeclaredThrowableException} so that this class can get + * access to the cause. + */ + private static class ChainingListenableFuture + extends AbstractFuture implements Runnable { + + private AsyncFunction function; + private ListenableFuture inputFuture; + private volatile ListenableFuture outputFuture; + private final BlockingQueue mayInterruptIfRunningChannel = + new LinkedBlockingQueue(1); + private final CountDownLatch outputCreated = new CountDownLatch(1); + + private ChainingListenableFuture( + AsyncFunction function, + ListenableFuture inputFuture) { + this.function = checkNotNull(function); + this.inputFuture = checkNotNull(inputFuture); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + /* + * Our additional cancellation work needs to occur even if + * !mayInterruptIfRunning, so we can't move it into interruptTask(). + */ + if (super.cancel(mayInterruptIfRunning)) { + // This should never block since only one thread is allowed to cancel + // this Future. + putUninterruptibly(mayInterruptIfRunningChannel, mayInterruptIfRunning); + cancel(inputFuture, mayInterruptIfRunning); + cancel(outputFuture, mayInterruptIfRunning); + return true; + } + return false; + } + + private void cancel(@Nullable Future future, + boolean mayInterruptIfRunning) { + if (future != null) { + future.cancel(mayInterruptIfRunning); + } + } + + @Override + public void run() { + try { + I sourceResult; + try { + sourceResult = getUninterruptibly(inputFuture); + } catch (CancellationException e) { + // Cancel this future and return. + // At this point, inputFuture is cancelled and outputFuture doesn't + // exist, so the value of mayInterruptIfRunning is irrelevant. + cancel(false); + return; + } catch (ExecutionException e) { + // Set the cause of the exception as this future's exception + setException(e.getCause()); + return; + } + + final ListenableFuture outputFuture = this.outputFuture = + function.apply(sourceResult); + if (isCancelled()) { + // Handles the case where cancel was called while the function was + // being applied. + // There is a gap in cancel(boolean) between calling sync.cancel() + // and storing the value of mayInterruptIfRunning, so this thread + // needs to block, waiting for that value. + outputFuture.cancel( + takeUninterruptibly(mayInterruptIfRunningChannel)); + this.outputFuture = null; + return; + } + outputFuture.addListener(new Runnable() { + @Override + public void run() { + try { + // Here it would have been nice to have had an + // UninterruptibleListenableFuture, but we don't want to start a + // combinatorial explosion of interfaces, so we have to make do. + set(getUninterruptibly(outputFuture)); + } catch (CancellationException e) { + // Cancel this future and return. + // At this point, inputFuture and outputFuture are done, so the + // value of mayInterruptIfRunning is irrelevant. + cancel(false); + return; + } catch (ExecutionException e) { + // Set the cause of the exception as this future's exception + setException(e.getCause()); + } finally { + // Don't pin inputs beyond completion + ChainingListenableFuture.this.outputFuture = null; + } + } + }, MoreExecutors.sameThreadExecutor()); + } catch (UndeclaredThrowableException e) { + // Set the cause of the exception as this future's exception + setException(e.getCause()); + } catch (Exception e) { + // This exception is irrelevant in this thread, but useful for the + // client + setException(e); + } catch (Error e) { + // Propagate errors up ASAP - our superclass will rethrow the error + setException(e); + } finally { + // Don't pin inputs beyond completion + function = null; + inputFuture = null; + // Allow our get routines to examine outputFuture now. + outputCreated.countDown(); + } + } + } + + /** + * Returns a new {@code ListenableFuture} whose result is the product of + * calling {@code get()} on the {@code Future} nested within the given {@code + * Future}, effectively chaining the futures one after the other. Example: + * + *

       {@code
    +   *   SettableFuture> nested = SettableFuture.create();
    +   *   ListenableFuture dereferenced = dereference(nested);
    +   * }
    + * + *

    This call has the same cancellation and execution semantics as {@link + * #transform(ListenableFuture, AsyncFunction)}, in that the returned {@code + * Future} attempts to keep its cancellation state in sync with both the + * input {@code Future} and the nested {@code Future}. The transformation + * is very lightweight and therefore takes place in the thread that called + * {@code dereference}. + * + * @param nested The nested future to transform. + * @return A future that holds result of the inner future. + * @since 13.0 + */ + @Beta + @SuppressWarnings({"rawtypes", "unchecked"}) + public static ListenableFuture dereference( + ListenableFuture> nested) { + return Futures.transform((ListenableFuture) nested, (AsyncFunction) DEREFERENCER); + } + + /** + * Helper {@code Function} for {@link #dereference}. + */ + private static final AsyncFunction, Object> DEREFERENCER = + new AsyncFunction, Object>() { + @Override public ListenableFuture apply(ListenableFuture input) { + return input; + } + }; + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the + * values of all its input futures, if all succeed. If any input fails, the + * returned future fails. + * + *

    The list of results is in the same order as the input list. + * + *

    Canceling this future does not cancel any of the component futures; + * however, if any of the provided futures fails or is canceled, this one is, + * too. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component + * futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> allAsList( + ListenableFuture... futures) { + return new ListFuture(ImmutableList.copyOf(futures), true, + MoreExecutors.sameThreadExecutor()); + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the + * values of all its input futures, if all succeed. If any input fails, the + * returned future fails. + * + *

    The list of results is in the same order as the input list. + * + *

    Canceling this future does not cancel any of the component futures; + * however, if any of the provided futures fails or is canceled, this one is, + * too. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component + * futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> allAsList( + Iterable> futures) { + return new ListFuture(ImmutableList.copyOf(futures), true, + MoreExecutors.sameThreadExecutor()); + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the + * values of all its successful input futures. The list of results is in the + * same order as the input list, and if any of the provided futures fails or + * is canceled, its corresponding position will contain {@code null} (which is + * indistinguishable from the future having a successful value of + * {@code null}). + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component + * futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> successfulAsList( + ListenableFuture... futures) { + return new ListFuture(ImmutableList.copyOf(futures), false, + MoreExecutors.sameThreadExecutor()); + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the + * values of all its successful input futures. The list of results is in the + * same order as the input list, and if any of the provided futures fails or + * is canceled, its corresponding position will contain {@code null} (which is + * indistinguishable from the future having a successful value of + * {@code null}). + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component + * futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> successfulAsList( + Iterable> futures) { + return new ListFuture(ImmutableList.copyOf(futures), false, + MoreExecutors.sameThreadExecutor()); + } + + /** + * Registers separate success and failure callbacks to be run when the {@code + * Future}'s computation is {@linkplain java.util.concurrent.Future#isDone() + * complete} or, if the computation is already complete, immediately. + * + *

    There is no guaranteed ordering of execution of callbacks, but any + * callback added through this method is guaranteed to be called once the + * computation is complete. + * + * Example:

     {@code
    +   * ListenableFuture future = ...;
    +   * addCallback(future,
    +   *     new FutureCallback {
    +   *       public void onSuccess(QueryResult result) {
    +   *         storeInCache(result);
    +   *       }
    +   *       public void onFailure(Throwable t) {
    +   *         reportError(t);
    +   *       }
    +   *     });}
    + * + * Note: If the callback is slow or heavyweight, consider {@linkplain + * #addCallback(ListenableFuture, FutureCallback, Executor) supplying an + * executor}. If you do not supply an executor, {@code addCallback} will use + * {@link MoreExecutors#sameThreadExecutor sameThreadExecutor}, which carries + * some caveats for heavier operations. For example, the callback may run on + * an unpredictable or undesirable thread: + * + *
      + *
    • If the input {@code Future} is done at the time {@code addCallback} is + * called, {@code addCallback} will execute the callback inline. + *
    • If the input {@code Future} is not yet done, {@code addCallback} will + * schedule the callback to be run by the thread that completes the input + * {@code Future}, which may be an internal system thread such as an RPC + * network thread. + *
    + * + * Also note that, regardless of which thread executes the callback, all + * other registered but unexecuted listeners are prevented from running + * during its execution, even if those listeners are to run in other + * executors. + * + *

    For a more general interface to attach a completion listener to a + * {@code Future}, see {@link ListenableFuture#addListener addListener}. + * + * @param future The future attach the callback to. + * @param callback The callback to invoke when {@code future} is completed. + * @since 10.0 + */ + public static void addCallback(ListenableFuture future, + FutureCallback callback) { + addCallback(future, callback, MoreExecutors.sameThreadExecutor()); + } + + /** + * Registers separate success and failure callbacks to be run when the {@code + * Future}'s computation is {@linkplain java.util.concurrent.Future#isDone() + * complete} or, if the computation is already complete, immediately. + * + *

    The callback is run in {@code executor}. + * There is no guaranteed ordering of execution of callbacks, but any + * callback added through this method is guaranteed to be called once the + * computation is complete. + * + * Example:

     {@code
    +   * ListenableFuture future = ...;
    +   * Executor e = ...
    +   * addCallback(future, e,
    +   *     new FutureCallback {
    +   *       public void onSuccess(QueryResult result) {
    +   *         storeInCache(result);
    +   *       }
    +   *       public void onFailure(Throwable t) {
    +   *         reportError(t);
    +   *       }
    +   *     });}
    + * + * When the callback is fast and lightweight, consider {@linkplain + * #addCallback(ListenableFuture, FutureCallback) omitting the executor} or + * explicitly specifying {@code sameThreadExecutor}. However, be aware of the + * caveats documented in the link above. + * + *

    For a more general interface to attach a completion listener to a + * {@code Future}, see {@link ListenableFuture#addListener addListener}. + * + * @param future The future attach the callback to. + * @param callback The callback to invoke when {@code future} is completed. + * @param executor The executor to run {@code callback} when the future + * completes. + * @since 10.0 + */ + public static void addCallback(final ListenableFuture future, + final FutureCallback callback, Executor executor) { + Preconditions.checkNotNull(callback); + Runnable callbackListener = new Runnable() { + @Override + public void run() { + try { + // TODO(user): (Before Guava release), validate that this + // is the thing for IE. + V value = getUninterruptibly(future); + callback.onSuccess(value); + } catch (ExecutionException e) { + callback.onFailure(e.getCause()); + } catch (RuntimeException e) { + callback.onFailure(e); + } catch (Error e) { + callback.onFailure(e); + } + } + }; + future.addListener(callbackListener, executor); + } + + /** + * Returns the result of {@link Future#get()}, converting most exceptions to a + * new instance of the given checked exception type. This reduces boilerplate + * for a common use of {@code Future} in which it is unnecessary to + * programmatically distinguish between exception types or to extract other + * information from the exception instance. + * + *

    Exceptions from {@code Future.get} are treated as follows: + *

      + *
    • Any {@link ExecutionException} has its cause wrapped in an + * {@code X} if the cause is a checked exception, an {@link + * UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an + * {@code Error}. + *
    • Any {@link InterruptedException} is wrapped in an {@code X} (after + * restoring the interrupt). + *
    • Any {@link CancellationException} is propagated untouched, as is any + * other {@link RuntimeException} (though {@code get} implementations are + * discouraged from throwing such exceptions). + *
    + * + * The overall principle is to continue to treat every checked exception as a + * checked exception, every unchecked exception as an unchecked exception, and + * every error as an error. In addition, the cause of any {@code + * ExecutionException} is wrapped in order to ensure that the new stack trace + * matches that of the current thread. + * + *

    Instances of {@code exceptionClass} are created by choosing an arbitrary + * public constructor that accepts zero or more arguments, all of type {@code + * String} or {@code Throwable} (preferring constructors with at least one + * {@code String}) and calling the constructor via reflection. If the + * exception did not already have a cause, one is set by calling {@link + * Throwable#initCause(Throwable)} on it. If no such constructor exists, an + * {@code IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code + * ExecutionException} whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code + * ExecutionException} with a {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} + * with an {@code Error} as its cause + * @throws CancellationException if {@code get} throws a {@code + * CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code + * RuntimeException} or does not have a suitable constructor + * @since 10.0 + */ + @Beta + public static V get( + Future future, Class exceptionClass) throws X { + checkNotNull(future); + checkArgument(!RuntimeException.class.isAssignableFrom(exceptionClass), + "Futures.get exception type (%s) must not be a RuntimeException", + exceptionClass); + try { + return future.get(); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw newWithCause(exceptionClass, e); + } catch (ExecutionException e) { + wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); + throw new AssertionError(); + } + } + + /** + * Returns the result of {@link Future#get(long, TimeUnit)}, converting most + * exceptions to a new instance of the given checked exception type. This + * reduces boilerplate for a common use of {@code Future} in which it is + * unnecessary to programmatically distinguish between exception types or to + * extract other information from the exception instance. + * + *

    Exceptions from {@code Future.get} are treated as follows: + *

      + *
    • Any {@link ExecutionException} has its cause wrapped in an + * {@code X} if the cause is a checked exception, an {@link + * UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an + * {@code Error}. + *
    • Any {@link InterruptedException} is wrapped in an {@code X} (after + * restoring the interrupt). + *
    • Any {@link TimeoutException} is wrapped in an {@code X}. + *
    • Any {@link CancellationException} is propagated untouched, as is any + * other {@link RuntimeException} (though {@code get} implementations are + * discouraged from throwing such exceptions). + *
    + * + * The overall principle is to continue to treat every checked exception as a + * checked exception, every unchecked exception as an unchecked exception, and + * every error as an error. In addition, the cause of any {@code + * ExecutionException} is wrapped in order to ensure that the new stack trace + * matches that of the current thread. + * + *

    Instances of {@code exceptionClass} are created by choosing an arbitrary + * public constructor that accepts zero or more arguments, all of type {@code + * String} or {@code Throwable} (preferring constructors with at least one + * {@code String}) and calling the constructor via reflection. If the + * exception did not already have a cause, one is set by calling {@link + * Throwable#initCause(Throwable)} on it. If no such constructor exists, an + * {@code IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code + * ExecutionException} whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code + * ExecutionException} with a {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} + * with an {@code Error} as its cause + * @throws CancellationException if {@code get} throws a {@code + * CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code + * RuntimeException} or does not have a suitable constructor + * @since 10.0 + */ + @Beta + public static V get( + Future future, long timeout, TimeUnit unit, Class exceptionClass) + throws X { + checkNotNull(future); + checkNotNull(unit); + checkArgument(!RuntimeException.class.isAssignableFrom(exceptionClass), + "Futures.get exception type (%s) must not be a RuntimeException", + exceptionClass); + try { + return future.get(timeout, unit); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw newWithCause(exceptionClass, e); + } catch (TimeoutException e) { + throw newWithCause(exceptionClass, e); + } catch (ExecutionException e) { + wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); + throw new AssertionError(); + } + } + + private static void wrapAndThrowExceptionOrError( + Throwable cause, Class exceptionClass) throws X { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } + if (cause instanceof RuntimeException) { + throw new UncheckedExecutionException(cause); + } + throw newWithCause(exceptionClass, cause); + } + + /** + * Returns the result of calling {@link Future#get()} uninterruptibly on a + * task known not to throw a checked exception. This makes {@code Future} more + * suitable for lightweight, fast-running tasks that, barring bugs in the + * code, will not fail. This gives it exception-handling behavior similar to + * that of {@code ForkJoinTask.join}. + * + *

    Exceptions from {@code Future.get} are treated as follows: + *

      + *
    • Any {@link ExecutionException} has its cause wrapped in an + * {@link UncheckedExecutionException} (if the cause is an {@code + * Exception}) or {@link ExecutionError} (if the cause is an {@code + * Error}). + *
    • Any {@link InterruptedException} causes a retry of the {@code get} + * call. The interrupt is restored before {@code getUnchecked} returns. + *
    • Any {@link CancellationException} is propagated untouched. So is any + * other {@link RuntimeException} ({@code get} implementations are + * discouraged from throwing such exceptions). + *
    + * + * The overall principle is to eliminate all checked exceptions: to loop to + * avoid {@code InterruptedException}, to pass through {@code + * CancellationException}, and to wrap any exception from the underlying + * computation in an {@code UncheckedExecutionException} or {@code + * ExecutionError}. + * + *

    For an uninterruptible {@code get} that preserves other exceptions, see + * {@link Uninterruptibles#getUninterruptibly(Future)}. + * + * @throws UncheckedExecutionException if {@code get} throws an {@code + * ExecutionException} with an {@code Exception} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} + * with an {@code Error} as its cause + * @throws CancellationException if {@code get} throws a {@code + * CancellationException} + * @since 10.0 + */ + @Beta + public static V getUnchecked(Future future) { + checkNotNull(future); + try { + return getUninterruptibly(future); + } catch (ExecutionException e) { + wrapAndThrowUnchecked(e.getCause()); + throw new AssertionError(); + } + } + + private static void wrapAndThrowUnchecked(Throwable cause) { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } + /* + * It's a non-Error, non-Exception Throwable. From my survey of such + * classes, I believe that most users intended to extend Exception, so we'll + * treat it like an Exception. + */ + throw new UncheckedExecutionException(cause); + } + + /* + * TODO(user): FutureChecker interface for these to be static methods on? If + * so, refer to it in the (static-method) Futures.get documentation + */ + + /* + * Arguably we don't need a timed getUnchecked because any operation slow + * enough to require a timeout is heavyweight enough to throw a checked + * exception and therefore be inappropriate to use with getUnchecked. Further, + * it's not clear that converting the checked TimeoutException to a + * RuntimeException -- especially to an UncheckedExecutionException, since it + * wasn't thrown by the computation -- makes sense, and if we don't convert + * it, the user still has to write a try-catch block. + * + * If you think you would use this method, let us know. + */ + + private static X newWithCause( + Class exceptionClass, Throwable cause) { + // getConstructors() guarantees this as long as we don't modify the array. + @SuppressWarnings("unchecked") + List> constructors = + (List) Arrays.asList(exceptionClass.getConstructors()); + for (Constructor constructor : preferringStrings(constructors)) { + @Nullable X instance = newFromConstructor(constructor, cause); + if (instance != null) { + if (instance.getCause() == null) { + instance.initCause(cause); + } + return instance; + } + } + throw new IllegalArgumentException( + "No appropriate constructor for exception of type " + exceptionClass + + " in response to chained exception", cause); + } + + private static List> + preferringStrings(List> constructors) { + return WITH_STRING_PARAM_FIRST.sortedCopy(constructors); + } + + private static final Ordering> WITH_STRING_PARAM_FIRST = + Ordering.natural().onResultOf(new Function, Boolean>() { + @Override public Boolean apply(Constructor input) { + return asList(input.getParameterTypes()).contains(String.class); + } + }).reverse(); + + @Nullable private static X newFromConstructor( + Constructor constructor, Throwable cause) { + Class[] paramTypes = constructor.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + Class paramType = paramTypes[i]; + if (paramType.equals(String.class)) { + params[i] = cause.toString(); + } else if (paramType.equals(Throwable.class)) { + params[i] = cause; + } else { + return null; + } + } + try { + return constructor.newInstance(params); + } catch (IllegalArgumentException e) { + return null; + } catch (InstantiationException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + } + + /** + * Class that implements {@link #allAsList} and {@link #successfulAsList}. + * The idea is to create a (null-filled) List and register a listener with + * each component future to fill out the value in the List when that future + * completes. + */ + private static class ListFuture extends AbstractFuture> { + ImmutableList> futures; + final boolean allMustSucceed; + final AtomicInteger remaining; + List values; + + /** + * Constructor. + * + * @param futures all the futures to build the list from + * @param allMustSucceed whether a single failure or cancellation should + * propagate to this future + * @param listenerExecutor used to run listeners on all the passed in + * futures. + */ + ListFuture( + final ImmutableList> futures, + final boolean allMustSucceed, final Executor listenerExecutor) { + this.futures = futures; + this.values = Lists.newArrayListWithCapacity(futures.size()); + this.allMustSucceed = allMustSucceed; + this.remaining = new AtomicInteger(futures.size()); + + init(listenerExecutor); + } + + private void init(final Executor listenerExecutor) { + // First, schedule cleanup to execute when the Future is done. + addListener(new Runnable() { + @Override + public void run() { + // By now the values array has either been set as the Future's value, + // or (in case of failure) is no longer useful. + ListFuture.this.values = null; + + // Let go of the memory held by other futures + ListFuture.this.futures = null; + } + }, MoreExecutors.sameThreadExecutor()); + + // Now begin the "real" initialization. + + // Corner case: List is empty. + if (futures.isEmpty()) { + set(Lists.newArrayList(values)); + return; + } + + // Populate the results list with null initially. + for (int i = 0; i < futures.size(); ++i) { + values.add(null); + } + + // Register a listener on each Future in the list to update + // the state of this future. + // Note that if all the futures on the list are done prior to completing + // this loop, the last call to addListener() will callback to + // setOneValue(), transitively call our cleanup listener, and set + // this.futures to null. + // We store a reference to futures to avoid the NPE. + ImmutableList> localFutures = futures; + for (int i = 0; i < localFutures.size(); i++) { + final ListenableFuture listenable = localFutures.get(i); + final int index = i; + listenable.addListener(new Runnable() { + @Override + public void run() { + setOneValue(index, listenable); + } + }, listenerExecutor); + } + } + + /** + * Sets the value at the given index to that of the given future. + */ + private void setOneValue(int index, Future future) { + List localValues = values; + if (isDone() || localValues == null) { + // Some other future failed or has been cancelled, causing this one to + // also be cancelled or have an exception set. This should only happen + // if allMustSucceed is true. + checkState(allMustSucceed, + "Future was done before all dependencies completed"); + return; + } + + try { + checkState(future.isDone(), + "Tried to set value from future which is not done"); + localValues.set(index, getUninterruptibly(future)); + } catch (CancellationException e) { + if (allMustSucceed) { + // Set ourselves as cancelled. Let the input futures keep running + // as some of them may be used elsewhere. + // (Currently we don't override interruptTask, so + // mayInterruptIfRunning==false isn't technically necessary.) + cancel(false); + } + } catch (ExecutionException e) { + if (allMustSucceed) { + // As soon as the first one fails, throw the exception up. + // The result of all other inputs is then ignored. + setException(e.getCause()); + } + } catch (RuntimeException e) { + if (allMustSucceed) { + setException(e); + } + } catch (Error e) { + // Propagate errors up ASAP - our superclass will rethrow the error + setException(e); + } finally { + int newRemaining = remaining.decrementAndGet(); + checkState(newRemaining >= 0, "Less than 0 remaining futures"); + if (newRemaining == 0) { + localValues = values; + if (localValues != null) { + set(Lists.newArrayList(localValues)); + } else { + checkState(isDone()); + } + } + } + } + + } + + /** + * A checked future that uses a function to map from exceptions to the + * appropriate checked type. + */ + private static class MappingCheckedFuture extends + AbstractCheckedFuture { + + final Function mapper; + + MappingCheckedFuture(ListenableFuture delegate, + Function mapper) { + super(delegate); + + this.mapper = checkNotNull(mapper); + } + + @Override + protected X mapException(Exception e) { + return mapper.apply(e); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java b/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java new file mode 100644 index 0000000..645a648 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Utilities necessary for working with libraries that supply plain {@link + * Future} instances. Note that, whenver possible, it is strongly preferred to + * modify those libraries to return {@code ListenableFuture} directly. + * + * @author Sven Mawson + * @since 10.0 (replacing {@code Futures.makeListenable}, which + * existed in 1.0) + */ +@Beta +public final class JdkFutureAdapters { + /** + * Assigns a thread to the given {@link Future} to provide {@link + * ListenableFuture} functionality. + * + *

    Warning: If the input future does not already implement {@code + * ListenableFuture}, the returned future will emulate {@link + * ListenableFuture#addListener} by taking a thread from an internal, + * unbounded pool at the first call to {@code addListener} and holding it + * until the future is {@linkplain Future#isDone() done}. + * + *

    Prefer to create {@code ListenableFuture} instances with {@link + * SettableFuture}, {@link MoreExecutors#listeningDecorator( + * java.util.concurrent.ExecutorService)}, {@link ListenableFutureTask}, + * {@link AbstractFuture}, and other utilities over creating plain {@code + * Future} instances to be upgraded to {@code ListenableFuture} after the + * fact. + */ + public static ListenableFuture listenInPoolThread( + Future future) { + if (future instanceof ListenableFuture) { + return (ListenableFuture) future; + } + return new ListenableFutureAdapter(future); + } + + /** + * Submits a blocking task for the given {@link Future} to provide {@link + * ListenableFuture} functionality. + * + *

    Warning: If the input future does not already implement {@code + * ListenableFuture}, the returned future will emulate {@link + * ListenableFuture#addListener} by submitting a task to the given executor at + * at the first call to {@code addListener}. The task must be started by the + * executor promptly, or else the returned {@code ListenableFuture} may fail + * to work. The task's execution consists of blocking until the input future + * is {@linkplain Future#isDone() done}, so each call to this method may + * claim and hold a thread for an arbitrary length of time. Use of bounded + * executors or other executors that may fail to execute a task promptly may + * result in deadlocks. + * + *

    Prefer to create {@code ListenableFuture} instances with {@link + * SettableFuture}, {@link MoreExecutors#listeningDecorator( + * java.util.concurrent.ExecutorService)}, {@link ListenableFutureTask}, + * {@link AbstractFuture}, and other utilities over creating plain {@code + * Future} instances to be upgraded to {@code ListenableFuture} after the + * fact. + * + * @since 12.0 + */ + public static ListenableFuture listenInPoolThread( + Future future, Executor executor) { + checkNotNull(executor); + if (future instanceof ListenableFuture) { + return (ListenableFuture) future; + } + return new ListenableFutureAdapter(future, executor); + } + + /** + * An adapter to turn a {@link Future} into a {@link ListenableFuture}. This + * will wait on the future to finish, and when it completes, run the + * listeners. This implementation will wait on the source future + * indefinitely, so if the source future never completes, the adapter will + * never complete either. + * + *

    If the delegate future is interrupted or throws an unexpected unchecked + * exception, the listeners will not be invoked. + */ + private static class ListenableFutureAdapter extends ForwardingFuture + implements ListenableFuture { + + private static final ThreadFactory threadFactory = + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("ListenableFutureAdapter-thread-%d") + .build(); + private static final Executor defaultAdapterExecutor = + Executors.newCachedThreadPool(threadFactory); + + private final Executor adapterExecutor; + + // The execution list to hold our listeners. + private final ExecutionList executionList = new ExecutionList(); + + // This allows us to only start up a thread waiting on the delegate future + // when the first listener is added. + private final AtomicBoolean hasListeners = new AtomicBoolean(false); + + // The delegate future. + private final Future delegate; + + ListenableFutureAdapter(Future delegate) { + this(delegate, defaultAdapterExecutor); + } + + ListenableFutureAdapter(Future delegate, Executor adapterExecutor) { + this.delegate = checkNotNull(delegate); + this.adapterExecutor = checkNotNull(adapterExecutor); + } + + @Override + protected Future delegate() { + return delegate; + } + + @Override + public void addListener(Runnable listener, Executor exec) { + executionList.add(listener, exec); + + // When a listener is first added, we run a task that will wait for + // the delegate to finish, and when it is done will run the listeners. + if (hasListeners.compareAndSet(false, true)) { + if (delegate.isDone()) { + // If the delegate is already done, run the execution list + // immediately on the current thread. + executionList.execute(); + return; + } + + adapterExecutor.execute(new Runnable() { + @Override + public void run() { + try { + delegate.get(); + } catch (Error e) { + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // Threads from our private pool are never interrupted. + throw new AssertionError(e); + } catch (Throwable e) { + // ExecutionException / CancellationException / RuntimeException + // The task is done, run the listeners. + } + executionList.execute(); + } + }); + } + } + } + + private JdkFutureAdapters() {} +} diff --git a/guava/src/com/google/common/util/concurrent/ListenableFuture.java b/guava/src/com/google/common/util/concurrent/ListenableFuture.java new file mode 100644 index 0000000..eb05354 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ListenableFuture.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; + +/** + * A {@link Future} that accepts completion listeners. Each listener has an + * associated executor, and it is invoked using this executor once the future's + * computation is {@linkplain Future#isDone() complete}. If the computation has + * already completed when the listener is added, the listener will execute + * immediately. + * + *

    See the Guava User Guide article on + * {@code ListenableFuture}. + * + *

    Purpose

    + * + * Most commonly, {@code ListenableFuture} is used as an input to another + * derived {@code Future}, as in {@link Futures#allAsList(Iterable) + * Futures.allAsList}. Many such methods are impossible to implement efficiently + * without listener support. + * + *

    It is possible to call {@link #addListener addListener} directly, but this + * is uncommon because the {@code Runnable} interface does not provide direct + * access to the {@code Future} result. (Users who want such access may prefer + * {@link Futures#addCallback Futures.addCallback}.) Still, direct {@code + * addListener} calls are occasionally useful:

       {@code
    + *   final String name = ...;
    + *   inFlight.add(name);
    + *   ListenableFuture future = service.query(name);
    + *   future.addListener(new Runnable() {
    + *     public void run() {
    + *       processedCount.incrementAndGet();
    + *       inFlight.remove(name);
    + *       lastProcessed.set(name);
    + *       logger.info("Done with {0}", name);
    + *     }
    + *   }, executor);}
    + * + *

    How to get an instance

    + * + * Developers are encouraged to return {@code ListenableFuture} from their + * methods so that users can take advantages of the utilities built atop the + * class. The way that they will create {@code ListenableFuture} instances + * depends on how they currently create {@code Future} instances: + *
      + *
    • If they are returned from an {@code ExecutorService}, convert that + * service to a {@link ListeningExecutorService}, usually by calling {@link + * MoreExecutors#listeningDecorator(ExecutorService) + * MoreExecutors.listeningDecorator}. (Custom executors may find it more + * convenient to use {@link ListenableFutureTask} directly.) + *
    • If they are manually filled in by a call to {@link FutureTask#set} or a + * similar method, create a {@link SettableFuture} instead. (Users with more + * complex needs may prefer {@link AbstractFuture}.) + *
    + * + * Occasionally, an API will return a plain {@code Future} and it will be + * impossible to change the return type. For this case, we provide a more + * expensive workaround in {@code JdkFutureAdapters}. However, when possible, it + * is more efficient and reliable to create a {@code ListenableFuture} directly. + * + * @author Sven Mawson + * @author Nishant Thakkar + * @since 1.0 + */ +public interface ListenableFuture extends Future { + /** + * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on + * the given executor. The listener will run when the {@code Future}'s + * computation is {@linkplain Future#isDone() complete} or, if the computation + * is already complete, immediately. + * + *

    There is no guaranteed ordering of execution of listeners, but any + * listener added through this method is guaranteed to be called once the + * computation is complete. + * + *

    Exceptions thrown by a listener will be propagated up to the executor. + * Any exception thrown during {@code Executor.execute} (e.g., a {@code + * RejectedExecutionException} or an exception thrown by {@linkplain + * MoreExecutors#sameThreadExecutor inline execution}) will be caught and + * logged. + * + *

    Note: For fast, lightweight listeners that would be safe to execute in + * any thread, consider {@link MoreExecutors#sameThreadExecutor}. For heavier + * listeners, {@code sameThreadExecutor()} carries some caveats. For + * example, the listener may run on an unpredictable or undesirable thread: + * + *

      + *
    • If the input {@code Future} is done at the time {@code addListener} is + * called, {@code addListener} will execute the listener inline. + *
    • If the input {@code Future} is not yet done, {@code addListener} will + * schedule the listener to be run by the thread that completes the input + * {@code Future}, which may be an internal system thread such as an RPC + * network thread. + *
    + * + * Also note that, regardless of which thread executes the listener, all + * other registered but unexecuted listeners are prevented from running + * during its execution, even if those listeners are to run in other + * executors. + * + *

    This is the most general listener interface. For common operations + * performed using listeners, see {@link + * com.google.common.util.concurrent.Futures}. For a simplified but general + * listener interface, see {@link + * com.google.common.util.concurrent.Futures#addCallback addCallback()}. + * + * @param listener the listener to run when the computation is complete + * @param executor the executor to run the listener in + * @throws NullPointerException if the executor or listener was null + * @throws RejectedExecutionException if we tried to execute the listener + * immediately but the executor rejected it. + */ + void addListener(Runnable listener, Executor executor); +} diff --git a/guava/src/com/google/common/util/concurrent/ListenableFutureTask.java b/guava/src/com/google/common/util/concurrent/ListenableFutureTask.java new file mode 100644 index 0000000..28c2a0a --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ListenableFutureTask.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; + +import javax.annotation.Nullable; + +/** + * A {@link FutureTask} that also implements the {@link ListenableFuture} + * interface. Unlike {@code FutureTask}, {@code ListenableFutureTask} does not + * provide an overrideable {@link FutureTask#done() done()} method. For similar + * functionality, call {@link #addListener}. + * + *

    + * + * @author Sven Mawson + * @since 1.0 + */ +public final class ListenableFutureTask extends FutureTask + implements ListenableFuture { + + // The execution list to hold our listeners. + private final ExecutionList executionList = new ExecutionList(); + + /** + * Creates a {@code ListenableFutureTask} that will upon running, execute the + * given {@code Callable}. + * + * @param callable the callable task + * @since 10.0 + */ + public static ListenableFutureTask create(Callable callable) { + return new ListenableFutureTask(callable); + } + + /** + * Creates a {@code ListenableFutureTask} that will upon running, execute the + * given {@code Runnable}, and arrange that {@code get} will return the + * given result on successful completion. + * + * @param runnable the runnable task + * @param result the result to return on successful completion. If you don't + * need a particular result, consider using constructions of the form: + * {@code ListenableFuture f = ListenableFutureTask.create(runnable, + * null)} + * @since 10.0 + */ + public static ListenableFutureTask create( + Runnable runnable, @Nullable V result) { + return new ListenableFutureTask(runnable, result); + } + + private ListenableFutureTask(Callable callable) { + super(callable); + } + + private ListenableFutureTask(Runnable runnable, @Nullable V result) { + super(runnable, result); + } + + @Override + public void addListener(Runnable listener, Executor exec) { + executionList.add(listener, exec); + } + + /** + * Internal implementation detail used to invoke the listeners. + */ + @Override + protected void done() { + executionList.execute(); + } +} diff --git a/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java b/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java new file mode 100644 index 0000000..9ea8e48 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * An {@link ExecutorService} that returns {@link ListenableFuture} instances. To create an instance + * from an existing {@link ExecutorService}, call + * {@link MoreExecutors#listeningDecorator(ExecutorService)}. + * + * @author Chris Povirk + * @since 10.0 + */ +public interface ListeningExecutorService extends ExecutorService { + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Callable task); + + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Runnable task); + + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Runnable task, T result); + + /** + * {@inheritDoc} + * + *

    All elements in the returned list must be {@link ListenableFuture} instances. + * + * @return A list of {@code ListenableFuture} instances representing the tasks, in the same + * sequential order as produced by the iterator for the given task list, each of which has + * completed. + * @throws RejectedExecutionException {@inheritDoc} + * @throws NullPointerException if any task is null + */ + @Override + List> invokeAll(Collection> tasks) + throws InterruptedException; + + /** + * {@inheritDoc} + * + *

    All elements in the returned list must be {@link ListenableFuture} instances. + * + * @return a list of {@code ListenableFuture} instances representing the tasks, in the same + * sequential order as produced by the iterator for the given task list. If the operation + * did not time out, each task will have completed. If it did time out, some of these + * tasks will not have completed. + * @throws RejectedExecutionException {@inheritDoc} + * @throws NullPointerException if any task is null + */ + @Override + List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException; +} diff --git a/guava/src/com/google/common/util/concurrent/ListeningScheduledExecutorService.java b/guava/src/com/google/common/util/concurrent/ListeningScheduledExecutorService.java new file mode 100644 index 0000000..42dcdd2 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ListeningScheduledExecutorService.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.ScheduledExecutorService; + +/** + * A {@link ScheduledExecutorService} that returns {@link ListenableFuture} + * instances from its {@code ExecutorService} methods. Futures returned by the + * {@code schedule*} methods, by contrast, need not implement {@code + * ListenableFuture}. (To create an instance from an existing {@link + * ScheduledExecutorService}, call {@link + * MoreExecutors#listeningDecorator(ScheduledExecutorService)}. + * + *

    TODO(cpovirk): make at least the one-time schedule() methods return a + * ListenableFuture, too? But then we'll need ListenableScheduledFuture... + * + * @author Chris Povirk + * @since 10.0 + */ +@Beta +public interface ListeningScheduledExecutorService + extends ScheduledExecutorService, ListeningExecutorService { +} diff --git a/guava/src/com/google/common/util/concurrent/Monitor.java b/guava/src/com/google/common/util/concurrent/Monitor.java new file mode 100644 index 0000000..d8c8693 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Monitor.java @@ -0,0 +1,942 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * A synchronization abstraction supporting waiting on arbitrary boolean conditions. + * + *

    This class is intended as a replacement for {@link ReentrantLock}. Code using {@code Monitor} + * is less error-prone and more readable than code using {@code ReentrantLock}, without significant + * performance loss. {@code Monitor} even has the potential for performance gain by optimizing the + * evaluation and signaling of conditions. Signaling is entirely + * + * implicit. + * By eliminating explicit signaling, this class can guarantee that only one thread is awakened + * when a condition becomes true (no "signaling storms" due to use of {@link + * java.util.concurrent.locks.Condition#signalAll Condition.signalAll}) and that no signals are lost + * (no "hangs" due to incorrect use of {@link java.util.concurrent.locks.Condition#signal + * Condition.signal}). + * + *

    A thread is said to occupy a monitor if it has entered the monitor but not yet + * left. Only one thread may occupy a given monitor at any moment. A monitor is also + * reentrant, so a thread may enter a monitor any number of times, and then must leave the same + * number of times. The enter and leave operations have the same synchronization + * semantics as the built-in Java language synchronization primitives. + * + *

    A call to any of the enter methods with void return type should always be + * followed immediately by a try/finally block to ensure that the current thread leaves the + * monitor cleanly:

       {@code
    + *
    + *   monitor.enter();
    + *   try {
    + *     // do things while occupying the monitor
    + *   } finally {
    + *     monitor.leave();
    + *   }}
    + * + * A call to any of the enter methods with boolean return type should always appear as + * the condition of an if statement containing a try/finally block to ensure that the + * current thread leaves the monitor cleanly:
       {@code
    + *
    + *   if (monitor.tryEnter()) {
    + *     try {
    + *       // do things while occupying the monitor
    + *     } finally {
    + *       monitor.leave();
    + *     }
    + *   } else {
    + *     // do other things since the monitor was not available
    + *   }}
    + * + *

    Comparison with {@code synchronized} and {@code ReentrantLock}

    + * + *

    The following examples show a simple threadsafe holder expressed using {@code synchronized}, + * {@link ReentrantLock}, and {@code Monitor}. + * + *

    {@code synchronized}

    + * + *

    This version is the fewest lines of code, largely because the synchronization mechanism used + * is built into the language and runtime. But the programmer has to remember to avoid a couple of + * common bugs: The {@code wait()} must be inside a {@code while} instead of an {@code if}, and + * {@code notifyAll()} must be used instead of {@code notify()} because there are two different + * logical conditions being awaited.

       {@code
    + *
    + *   public class SafeBox {
    + *     private V value;
    + *
    + *     public synchronized V get() throws InterruptedException {
    + *       while (value == null) {
    + *         wait();
    + *       }
    + *       V result = value;
    + *       value = null;
    + *       notifyAll();
    + *       return result;
    + *     }
    + *
    + *     public synchronized void set(V newValue) throws InterruptedException {
    + *       while (value != null) {
    + *         wait();
    + *       }
    + *       value = newValue;
    + *       notifyAll();
    + *     }
    + *   }}
    + * + *

    {@code ReentrantLock}

    + * + *

    This version is much more verbose than the {@code synchronized} version, and still suffers + * from the need for the programmer to remember to use {@code while} instead of {@code if}. + * However, one advantage is that we can introduce two separate {@code Condition} objects, which + * allows us to use {@code signal()} instead of {@code signalAll()}, which may be a performance + * benefit.

       {@code
    + *
    + *   public class SafeBox {
    + *     private final ReentrantLock lock = new ReentrantLock();
    + *     private final Condition valuePresent = lock.newCondition();
    + *     private final Condition valueAbsent = lock.newCondition();
    + *     private V value;
    + *
    + *     public V get() throws InterruptedException {
    + *       lock.lock();
    + *       try {
    + *         while (value == null) {
    + *           valuePresent.await();
    + *         }
    + *         V result = value;
    + *         value = null;
    + *         valueAbsent.signal();
    + *         return result;
    + *       } finally {
    + *         lock.unlock();
    + *       }
    + *     }
    + *
    + *     public void set(V newValue) throws InterruptedException {
    + *       lock.lock();
    + *       try {
    + *         while (value != null) {
    + *           valueAbsent.await();
    + *         }
    + *         value = newValue;
    + *         valuePresent.signal();
    + *       } finally {
    + *         lock.unlock();
    + *       }
    + *     }
    + *   }}
    + * + *

    {@code Monitor}

    + * + *

    This version adds some verbosity around the {@code Guard} objects, but removes that same + * verbosity, and more, from the {@code get} and {@code set} methods. {@code Monitor} implements the + * same efficient signaling as we had to hand-code in the {@code ReentrantLock} version above. + * Finally, the programmer no longer has to hand-code the wait loop, and therefore doesn't have to + * remember to use {@code while} instead of {@code if}.

       {@code
    + *
    + *   public class SafeBox {
    + *     private final Monitor monitor = new Monitor();
    + *     private final Monitor.Guard valuePresent = new Monitor.Guard(monitor) {
    + *       public boolean isSatisfied() {
    + *         return value != null;
    + *       }
    + *     };
    + *     private final Monitor.Guard valueAbsent = new Monitor.Guard(monitor) {
    + *       public boolean isSatisfied() {
    + *         return value == null;
    + *       }
    + *     };
    + *     private V value;
    + *
    + *     public V get() throws InterruptedException {
    + *       monitor.enterWhen(valuePresent);
    + *       try {
    + *         V result = value;
    + *         value = null;
    + *         return result;
    + *       } finally {
    + *         monitor.leave();
    + *       }
    + *     }
    + *
    + *     public void set(V newValue) throws InterruptedException {
    + *       monitor.enterWhen(valueAbsent);
    + *       try {
    + *         value = newValue;
    + *       } finally {
    + *         monitor.leave();
    + *       }
    + *     }
    + *   }}
    + * + * @author Justin T. Sampson + * @since 10.0 + */ +@Beta +public final class Monitor { + // TODO: Use raw LockSupport or AbstractQueuedSynchronizer instead of ReentrantLock. + + /** + * A boolean condition for which a thread may wait. A {@code Guard} is associated with a single + * {@code Monitor}. The monitor may check the guard at arbitrary times from any thread occupying + * the monitor, so code should not be written to rely on how often a guard might or might not be + * checked. + * + *

    If a {@code Guard} is passed into any method of a {@code Monitor} other than the one it is + * associated with, an {@link IllegalMonitorStateException} is thrown. + * + * @since 10.0 + */ + @Beta + public abstract static class Guard { + + final Monitor monitor; + final Condition condition; + + @GuardedBy("monitor.lock") + int waiterCount = 0; + + protected Guard(Monitor monitor) { + this.monitor = checkNotNull(monitor, "monitor"); + this.condition = monitor.lock.newCondition(); + } + + /** + * Evaluates this guard's boolean condition. This method is always called with the associated + * monitor already occupied. Implementations of this method must depend only on state protected + * by the associated monitor, and must not modify that state. + */ + public abstract boolean isSatisfied(); + + @Override + public final boolean equals(Object other) { + // Overridden as final to ensure identity semantics in Monitor.activeGuards. + return this == other; + } + + @Override + public final int hashCode() { + // Overridden as final to ensure identity semantics in Monitor.activeGuards. + return super.hashCode(); + } + + } + + /** + * Whether this monitor is fair. + */ + private final boolean fair; + + /** + * The lock underlying this monitor. + */ + private final ReentrantLock lock; + + /** + * The guards associated with this monitor that currently have waiters ({@code waiterCount > 0}). + * This is an ArrayList rather than, say, a HashSet so that iteration and almost all adds don't + * incur any object allocation overhead. + */ + @GuardedBy("lock") + private final ArrayList activeGuards = Lists.newArrayListWithCapacity(1); + + /** + * Creates a monitor with a non-fair (but fast) ordering policy. Equivalent to {@code + * Monitor(false)}. + */ + public Monitor() { + this(false); + } + + /** + * Creates a monitor with the given ordering policy. + * + * @param fair whether this monitor should use a fair ordering policy rather than a non-fair (but + * fast) one + */ + public Monitor(boolean fair) { + this.fair = fair; + this.lock = new ReentrantLock(fair); + } + + /** + * Enters this monitor. Blocks indefinitely. + */ + public void enter() { + lock.lock(); + } + + /** + * Enters this monitor. Blocks indefinitely, but may be interrupted. + */ + public void enterInterruptibly() throws InterruptedException { + lock.lockInterruptibly(); + } + + /** + * Enters this monitor. Blocks at most the given time. + * + * @return whether the monitor was entered + */ + public boolean enter(long time, TimeUnit unit) { + final ReentrantLock lock = this.lock; + if (!fair && lock.tryLock()) { + return true; + } + long startNanos = System.nanoTime(); + long timeoutNanos = unit.toNanos(time); + long remainingNanos = timeoutNanos; + boolean interruptIgnored = false; + try { + while (true) { + try { + return lock.tryLock(remainingNanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException ignored) { + interruptIgnored = true; + remainingNanos = (timeoutNanos - (System.nanoTime() - startNanos)); + } + } + } finally { + if (interruptIgnored) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Enters this monitor. Blocks at most the given time, and may be interrupted. + * + * @return whether the monitor was entered + */ + public boolean enterInterruptibly(long time, TimeUnit unit) throws InterruptedException { + return lock.tryLock(time, unit); + } + + /** + * Enters this monitor if it is possible to do so immediately. Does not block. + * + *

    Note: This method disregards the fairness setting of this monitor. + * + * @return whether the monitor was entered + */ + public boolean tryEnter() { + return lock.tryLock(); + } + + /** + * Enters this monitor when the guard is satisfied. Blocks indefinitely, but may be interrupted. + */ + public void enterWhen(Guard guard) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean reentrant = lock.isHeldByCurrentThread(); + boolean success = false; + lock.lockInterruptibly(); + try { + waitInterruptibly(guard, reentrant); + success = true; + } finally { + if (!success) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor when the guard is satisfied. Blocks indefinitely. + */ + public void enterWhenUninterruptibly(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean reentrant = lock.isHeldByCurrentThread(); + boolean success = false; + lock.lock(); + try { + waitUninterruptibly(guard, reentrant); + success = true; + } finally { + if (!success) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including both + * the time to acquire the lock and the time to wait for the guard to be satisfied, and may be + * interrupted. + * + * @return whether the monitor was entered + */ + public boolean enterWhen(Guard guard, long time, TimeUnit unit) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean reentrant = lock.isHeldByCurrentThread(); + long remainingNanos; + if (!fair && lock.tryLock()) { + remainingNanos = unit.toNanos(time); + } else { + long startNanos = System.nanoTime(); + if (!lock.tryLock(time, unit)) { + return false; + } + remainingNanos = unit.toNanos(time) - (System.nanoTime() - startNanos); + } + boolean satisfied = false; + try { + satisfied = waitInterruptibly(guard, remainingNanos, reentrant); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including + * both the time to acquire the lock and the time to wait for the guard to be satisfied. + * + * @return whether the monitor was entered + */ + public boolean enterWhenUninterruptibly(Guard guard, long time, TimeUnit unit) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean reentrant = lock.isHeldByCurrentThread(); + boolean interruptIgnored = false; + try { + long remainingNanos; + if (!fair && lock.tryLock()) { + remainingNanos = unit.toNanos(time); + } else { + long startNanos = System.nanoTime(); + long timeoutNanos = unit.toNanos(time); + remainingNanos = timeoutNanos; + while (true) { + try { + if (lock.tryLock(remainingNanos, TimeUnit.NANOSECONDS)) { + break; + } else { + return false; + } + } catch (InterruptedException ignored) { + interruptIgnored = true; + } finally { + remainingNanos = (timeoutNanos - (System.nanoTime() - startNanos)); + } + } + } + boolean satisfied = false; + try { + satisfied = waitUninterruptibly(guard, remainingNanos, reentrant); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } finally { + if (interruptIgnored) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Enters this monitor if the guard is satisfied. Blocks indefinitely acquiring the lock, but + * does not wait for the guard to be satisfied. + * + * @return whether the monitor was entered + */ + public boolean enterIf(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + lock.lock(); + boolean satisfied = false; + try { + satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Enters this monitor if the guard is satisfied. Blocks indefinitely acquiring the lock, but does + * not wait for the guard to be satisfied, and may be interrupted. + * + * @return whether the monitor was entered + */ + public boolean enterIfInterruptibly(Guard guard) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + boolean satisfied = false; + try { + satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied. + * + * @return whether the monitor was entered + */ + public boolean enterIf(Guard guard, long time, TimeUnit unit) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + if (!enter(time, unit)) { + return false; + } + boolean satisfied = false; + try { + satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied, and may be interrupted. + * + * @return whether the monitor was entered + */ + public boolean enterIfInterruptibly(Guard guard, long time, TimeUnit unit) + throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + if (!lock.tryLock(time, unit)) { + return false; + } + boolean satisfied = false; + try { + satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Enters this monitor if it is possible to do so immediately and the guard is satisfied. Does not + * block acquiring the lock and does not wait for the guard to be satisfied. + * + *

    Note: This method disregards the fairness setting of this monitor. + * + * @return whether the monitor was entered + */ + public boolean tryEnterIf(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + if (!lock.tryLock()) { + return false; + } + boolean satisfied = false; + try { + satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + return satisfied; + } + + /** + * Waits for the guard to be satisfied. Waits indefinitely, but may be interrupted. May be + * called only by a thread currently occupying this monitor. + */ + public void waitFor(Guard guard) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + waitInterruptibly(guard, true); + } + + /** + * Waits for the guard to be satisfied. Waits indefinitely. May be called only by a thread + * currently occupying this monitor. + */ + public void waitForUninterruptibly(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + waitUninterruptibly(guard, true); + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time, and may be interrupted. + * May be called only by a thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + */ + public boolean waitFor(Guard guard, long time, TimeUnit unit) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + return waitInterruptibly(guard, unit.toNanos(time), true); + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time. May be called only by a + * thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + */ + public boolean waitForUninterruptibly(Guard guard, long time, TimeUnit unit) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + return waitUninterruptibly(guard, unit.toNanos(time), true); + } + + /** + * Leaves this monitor. May be called only by a thread currently occupying this monitor. + */ + public void leave() { + final ReentrantLock lock = this.lock; + if (!lock.isHeldByCurrentThread()) { + throw new IllegalMonitorStateException(); + } + try { + signalConditionsOfSatisfiedGuards(null); + } finally { + lock.unlock(); + } + } + + /** + * Returns whether this monitor is using a fair ordering policy. + */ + public boolean isFair() { + return lock.isFair(); + } + + /** + * Returns whether this monitor is occupied by any thread. This method is designed for use in + * monitoring of the system state, not for synchronization control. + */ + public boolean isOccupied() { + return lock.isLocked(); + } + + /** + * Returns whether the current thread is occupying this monitor (has entered more times than it + * has left). + */ + public boolean isOccupiedByCurrentThread() { + return lock.isHeldByCurrentThread(); + } + + /** + * Returns the number of times the current thread has entered this monitor in excess of the number + * of times it has left. Returns 0 if the current thread is not occupying this monitor. + */ + public int getOccupiedDepth() { + return lock.getHoldCount(); + } + + /** + * Returns an estimate of the number of threads waiting to enter this monitor. The value is only + * an estimate because the number of threads may change dynamically while this method traverses + * internal data structures. This method is designed for use in monitoring of the system state, + * not for synchronization control. + */ + public int getQueueLength() { + return lock.getQueueLength(); + } + + /** + * Returns whether any threads are waiting to enter this monitor. Note that because cancellations + * may occur at any time, a {@code true} return does not guarantee that any other thread will ever + * enter this monitor. This method is designed primarily for use in monitoring of the system + * state. + */ + public boolean hasQueuedThreads() { + return lock.hasQueuedThreads(); + } + + /** + * Queries whether the given thread is waiting to enter this monitor. Note that because + * cancellations may occur at any time, a {@code true} return does not guarantee that this thread + * will ever enter this monitor. This method is designed primarily for use in monitoring of the + * system state. + */ + public boolean hasQueuedThread(Thread thread) { + return lock.hasQueuedThread(thread); + } + + /** + * Queries whether any threads are waiting for the given guard to become satisfied. Note that + * because timeouts and interrupts may occur at any time, a {@code true} return does not guarantee + * that the guard becoming satisfied in the future will awaken any threads. This method is + * designed primarily for use in monitoring of the system state. + */ + public boolean hasWaiters(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + lock.lock(); + try { + return guard.waiterCount > 0; + } finally { + lock.unlock(); + } + } + + /** + * Returns an estimate of the number of threads waiting for the given guard to become satisfied. + * Note that because timeouts and interrupts may occur at any time, the estimate serves only as an + * upper bound on the actual number of waiters. This method is designed for use in monitoring of + * the system state, not for synchronization control. + */ + public int getWaitQueueLength(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + lock.lock(); + try { + return guard.waiterCount; + } finally { + lock.unlock(); + } + } + + @GuardedBy("lock") + private void signalConditionsOfSatisfiedGuards(@Nullable Guard interruptedGuard) { + final ArrayList guards = this.activeGuards; + final int guardCount = guards.size(); + try { + for (int i = 0; i < guardCount; i++) { + Guard guard = guards.get(i); + if ((guard == interruptedGuard) && (guard.waiterCount == 1)) { + // That one waiter was just interrupted and is throwing InterruptedException rather than + // paying attention to the guard being satisfied, so find another waiter on another guard. + continue; + } + if (guard.isSatisfied()) { + guard.condition.signal(); + return; + } + } + } catch (Throwable throwable) { + for (int i = 0; i < guardCount; i++) { + Guard guard = guards.get(i); + guard.condition.signalAll(); + } + throw Throwables.propagate(throwable); + } + } + + @GuardedBy("lock") + private void incrementWaiters(Guard guard) { + int waiters = guard.waiterCount++; + if (waiters == 0) { + activeGuards.add(guard); + } + } + + @GuardedBy("lock") + private void decrementWaiters(Guard guard) { + int waiters = --guard.waiterCount; + if (waiters == 0) { + activeGuards.remove(guard); + } + } + + @GuardedBy("lock") + private void waitInterruptibly(Guard guard, boolean signalBeforeWaiting) + throws InterruptedException { + if (!guard.isSatisfied()) { + if (signalBeforeWaiting) { + signalConditionsOfSatisfiedGuards(null); + } + incrementWaiters(guard); + try { + final Condition condition = guard.condition; + do { + try { + condition.await(); + } catch (InterruptedException interrupt) { + try { + signalConditionsOfSatisfiedGuards(guard); + } catch (Throwable throwable) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(throwable); + } + throw interrupt; + } + } while (!guard.isSatisfied()); + } finally { + decrementWaiters(guard); + } + } + } + + @GuardedBy("lock") + private void waitUninterruptibly(Guard guard, boolean signalBeforeWaiting) { + if (!guard.isSatisfied()) { + if (signalBeforeWaiting) { + signalConditionsOfSatisfiedGuards(null); + } + incrementWaiters(guard); + try { + final Condition condition = guard.condition; + do { + condition.awaitUninterruptibly(); + } while (!guard.isSatisfied()); + } finally { + decrementWaiters(guard); + } + } + } + + @GuardedBy("lock") + private boolean waitInterruptibly(Guard guard, long remainingNanos, boolean signalBeforeWaiting) + throws InterruptedException { + if (!guard.isSatisfied()) { + if (signalBeforeWaiting) { + signalConditionsOfSatisfiedGuards(null); + } + incrementWaiters(guard); + try { + final Condition condition = guard.condition; + do { + if (remainingNanos <= 0) { + return false; + } + try { + remainingNanos = condition.awaitNanos(remainingNanos); + } catch (InterruptedException interrupt) { + try { + signalConditionsOfSatisfiedGuards(guard); + } catch (Throwable throwable) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(throwable); + } + throw interrupt; + } + } while (!guard.isSatisfied()); + } finally { + decrementWaiters(guard); + } + } + return true; + } + + @GuardedBy("lock") + private boolean waitUninterruptibly(Guard guard, long timeoutNanos, + boolean signalBeforeWaiting) { + if (!guard.isSatisfied()) { + long startNanos = System.nanoTime(); + if (signalBeforeWaiting) { + signalConditionsOfSatisfiedGuards(null); + } + boolean interruptIgnored = false; + try { + incrementWaiters(guard); + try { + final Condition condition = guard.condition; + long remainingNanos = timeoutNanos; + do { + if (remainingNanos <= 0) { + return false; + } + try { + remainingNanos = condition.awaitNanos(remainingNanos); + } catch (InterruptedException ignored) { + try { + signalConditionsOfSatisfiedGuards(guard); + } catch (Throwable throwable) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(throwable); + } + interruptIgnored = true; + remainingNanos = (timeoutNanos - (System.nanoTime() - startNanos)); + } + } while (!guard.isSatisfied()); + } finally { + decrementWaiters(guard); + } + } finally { + if (interruptIgnored) { + Thread.currentThread().interrupt(); + } + } + } + return true; + } + +} diff --git a/guava/src/com/google/common/util/concurrent/MoreExecutors.java b/guava/src/com/google/common/util/concurrent/MoreExecutors.java new file mode 100644 index 0000000..8699b5a --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/MoreExecutors.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Factory and utility methods for {@link java.util.concurrent.Executor}, {@link + * ExecutorService}, and {@link ThreadFactory}. + * + * @author Eric Fellheimer + * @author Kyle Littlefield + * @author Justin Mahoney + * @since 3.0 + */ +public final class MoreExecutors { + private MoreExecutors() {} + + /** + * Converts the given ThreadPoolExecutor into an ExecutorService that exits + * when the application is complete. It does so by using daemon threads and + * adding a shutdown hook to wait for their completion. + * + *

    This is mainly for fixed thread pools. + * See {@link Executors#newFixedThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the + * application is finished + * @param terminationTimeout how long to wait for the executor to + * finish before terminating the JVM + * @param timeUnit unit of time for the time parameter + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + public static ExecutorService getExitingExecutorService( + ThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) { + executor.setThreadFactory(new ThreadFactoryBuilder() + .setDaemon(true) + .setThreadFactory(executor.getThreadFactory()) + .build()); + + ExecutorService service = Executors.unconfigurableExecutorService(executor); + + addDelayedShutdownHook(service, terminationTimeout, timeUnit); + + return service; + } + + /** + * Converts the given ScheduledThreadPoolExecutor into a + * ScheduledExecutorService that exits when the application is complete. It + * does so by using daemon threads and adding a shutdown hook to wait for + * their completion. + * + *

    This is mainly for fixed thread pools. + * See {@link Executors#newScheduledThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the + * application is finished + * @param terminationTimeout how long to wait for the executor to + * finish before terminating the JVM + * @param timeUnit unit of time for the time parameter + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + public static ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor, long terminationTimeout, + TimeUnit timeUnit) { + executor.setThreadFactory(new ThreadFactoryBuilder() + .setDaemon(true) + .setThreadFactory(executor.getThreadFactory()) + .build()); + + ScheduledExecutorService service = + Executors.unconfigurableScheduledExecutorService(executor); + + addDelayedShutdownHook(service, terminationTimeout, timeUnit); + + return service; + } + + /** + * Add a shutdown hook to wait for thread completion in the given + * {@link ExecutorService service}. This is useful if the given service uses + * daemon threads, and we want to keep the JVM from exiting immediately on + * shutdown, instead giving these daemon threads a chance to terminate + * normally. + * @param service ExecutorService which uses daemon threads + * @param terminationTimeout how long to wait for the executor to finish + * before terminating the JVM + * @param timeUnit unit of time for the time parameter + */ + @Beta + public static void addDelayedShutdownHook( + final ExecutorService service, final long terminationTimeout, + final TimeUnit timeUnit) { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + try { + // We'd like to log progress and failures that may arise in the + // following code, but unfortunately the behavior of logging + // is undefined in shutdown hooks. + // This is because the logging code installs a shutdown hook of its + // own. See Cleaner class inside {@link LogManager}. + service.shutdown(); + service.awaitTermination(terminationTimeout, timeUnit); + } catch (InterruptedException ignored) { + // We're shutting down anyway, so just ignore. + } + } + }, "DelayedShutdownHook-for-" + service)); + } + + /** + * Converts the given ThreadPoolExecutor into an ExecutorService that exits + * when the application is complete. It does so by using daemon threads and + * adding a shutdown hook to wait for their completion. + * + *

    This method waits 120 seconds before continuing with JVM termination, + * even if the executor has not finished its work. + * + *

    This is mainly for fixed thread pools. + * See {@link Executors#newFixedThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the + * application is finished + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + public static ExecutorService getExitingExecutorService( + ThreadPoolExecutor executor) { + return getExitingExecutorService(executor, 120, TimeUnit.SECONDS); + } + + /** + * Converts the given ThreadPoolExecutor into a ScheduledExecutorService that + * exits when the application is complete. It does so by using daemon threads + * and adding a shutdown hook to wait for their completion. + * + *

    This method waits 120 seconds before continuing with JVM termination, + * even if the executor has not finished its work. + * + *

    This is mainly for fixed thread pools. + * See {@link Executors#newScheduledThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the + * application is finished + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + public static ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor) { + return getExitingScheduledExecutorService(executor, 120, TimeUnit.SECONDS); + } + + /** + * Creates an executor service that runs each task in the thread + * that invokes {@code execute/submit}, as in {@link CallerRunsPolicy} This + * applies both to individually submitted tasks and to collections of tasks + * submitted via {@code invokeAll} or {@code invokeAny}. In the latter case, + * tasks will run serially on the calling thread. Tasks are run to + * completion before a {@code Future} is returned to the caller (unless the + * executor has been shutdown). + * + *

    Although all tasks are immediately executed in the thread that + * submitted the task, this {@code ExecutorService} imposes a small + * locking overhead on each task submission in order to implement shutdown + * and termination behavior. + * + *

    The implementation deviates from the {@code ExecutorService} + * specification with regards to the {@code shutdownNow} method. First, + * "best-effort" with regards to canceling running tasks is implemented + * as "no-effort". No interrupts or other attempts are made to stop + * threads executing tasks. Second, the returned list will always be empty, + * as any submitted task is considered to have started execution. + * This applies also to tasks given to {@code invokeAll} or {@code invokeAny} + * which are pending serial execution, even the subset of the tasks that + * have not yet started execution. It is unclear from the + * {@code ExecutorService} specification if these should be included, and + * it's much easier to implement the interpretation that they not be. + * Finally, a call to {@code shutdown} or {@code shutdownNow} may result + * in concurrent calls to {@code invokeAll/invokeAny} throwing + * RejectedExecutionException, although a subset of the tasks may already + * have been executed. + * + * @since 10.0 (mostly source-compatible since 3.0) + */ + public static ListeningExecutorService sameThreadExecutor() { + return new SameThreadExecutorService(); + } + + // See sameThreadExecutor javadoc for behavioral notes. + private static class SameThreadExecutorService + extends AbstractListeningExecutorService { + /** + * Lock used whenever accessing the state variables + * (runningTasks, shutdown, terminationCondition) of the executor + */ + private final Lock lock = new ReentrantLock(); + + /** Signaled after the executor is shutdown and running tasks are done */ + private final Condition termination = lock.newCondition(); + + /* + * Conceptually, these two variables describe the executor being in + * one of three states: + * - Active: shutdown == false + * - Shutdown: runningTasks > 0 and shutdown == true + * - Terminated: runningTasks == 0 and shutdown == true + */ + private int runningTasks = 0; + private boolean shutdown = false; + + @Override + public void execute(Runnable command) { + startTask(); + try { + command.run(); + } finally { + endTask(); + } + } + + @Override + public boolean isShutdown() { + lock.lock(); + try { + return shutdown; + } finally { + lock.unlock(); + } + } + + @Override + public void shutdown() { + lock.lock(); + try { + shutdown = true; + } finally { + lock.unlock(); + } + } + + // See sameThreadExecutor javadoc for unusual behavior of this method. + @Override + public List shutdownNow() { + shutdown(); + return Collections.emptyList(); + } + + @Override + public boolean isTerminated() { + lock.lock(); + try { + return shutdown && runningTasks == 0; + } finally { + lock.unlock(); + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + long nanos = unit.toNanos(timeout); + lock.lock(); + try { + for (;;) { + if (isTerminated()) { + return true; + } else if (nanos <= 0) { + return false; + } else { + nanos = termination.awaitNanos(nanos); + } + } + } finally { + lock.unlock(); + } + } + + /** + * Checks if the executor has been shut down and increments the running + * task count. + * + * @throws RejectedExecutionException if the executor has been previously + * shutdown + */ + private void startTask() { + lock.lock(); + try { + if (isShutdown()) { + throw new RejectedExecutionException("Executor already shutdown"); + } + runningTasks++; + } finally { + lock.unlock(); + } + } + + /** + * Decrements the running task count. + */ + private void endTask() { + lock.lock(); + try { + runningTasks--; + if (isTerminated()) { + termination.signalAll(); + } + } finally { + lock.unlock(); + } + } + } + + /** + * Creates an {@link ExecutorService} whose {@code submit} and {@code + * invokeAll} methods submit {@link ListenableFutureTask} instances to the + * given delegate executor. Those methods, as well as {@code execute} and + * {@code invokeAny}, are implemented in terms of calls to {@code + * delegate.execute}. All other methods are forwarded unchanged to the + * delegate. This implies that the returned {@code ListeningExecutorService} + * never calls the delegate's {@code submit}, {@code invokeAll}, and {@code + * invokeAny} methods, so any special handling of tasks must be implemented in + * the delegate's {@code execute} method or by wrapping the returned {@code + * ListeningExecutorService}. + * + *

    If the delegate executor was already an instance of {@code + * ListeningExecutorService}, it is returned untouched, and the rest of this + * documentation does not apply. + * + * @since 10.0 + */ + public static ListeningExecutorService listeningDecorator( + ExecutorService delegate) { + return (delegate instanceof ListeningExecutorService) + ? (ListeningExecutorService) delegate + : (delegate instanceof ScheduledExecutorService) + ? new ScheduledListeningDecorator((ScheduledExecutorService) delegate) + : new ListeningDecorator(delegate); + } + + /** + * Creates a {@link ScheduledExecutorService} whose {@code submit} and {@code + * invokeAll} methods submit {@link ListenableFutureTask} instances to the + * given delegate executor. Those methods, as well as {@code execute} and + * {@code invokeAny}, are implemented in terms of calls to {@code + * delegate.execute}. All other methods are forwarded unchanged to the + * delegate. This implies that the returned {@code + * SchedulingListeningExecutorService} never calls the delegate's {@code + * submit}, {@code invokeAll}, and {@code invokeAny} methods, so any special + * handling of tasks must be implemented in the delegate's {@code execute} + * method or by wrapping the returned {@code + * SchedulingListeningExecutorService}. + * + *

    If the delegate executor was already an instance of {@code + * ListeningScheduledExecutorService}, it is returned untouched, and the rest + * of this documentation does not apply. + * + * @since 10.0 + */ + public static ListeningScheduledExecutorService listeningDecorator( + ScheduledExecutorService delegate) { + return (delegate instanceof ListeningScheduledExecutorService) + ? (ListeningScheduledExecutorService) delegate + : new ScheduledListeningDecorator(delegate); + } + + private static class ListeningDecorator + extends AbstractListeningExecutorService { + final ExecutorService delegate; + + ListeningDecorator(ExecutorService delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public void execute(Runnable command) { + delegate.execute(command); + } + } + + private static class ScheduledListeningDecorator + extends ListeningDecorator implements ListeningScheduledExecutorService { + @SuppressWarnings("hiding") + final ScheduledExecutorService delegate; + + ScheduledListeningDecorator(ScheduledExecutorService delegate) { + super(delegate); + this.delegate = checkNotNull(delegate); + } + + @Override + public ScheduledFuture schedule( + Runnable command, long delay, TimeUnit unit) { + return delegate.schedule(command, delay, unit); + } + + @Override + public ScheduledFuture schedule( + Callable callable, long delay, TimeUnit unit) { + return delegate.schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + return delegate.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + return delegate.scheduleWithFixedDelay( + command, initialDelay, delay, unit); + } + } + + /* + * This following method is a modified version of one found in + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/tck/AbstractExecutorServiceTest.java?revision=1.30 + * which contained the following notice: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + + /** + * An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService} + * implementations. + */ static T invokeAnyImpl(ListeningExecutorService executorService, + Collection> tasks, boolean timed, long nanos) + throws InterruptedException, ExecutionException, TimeoutException { + int ntasks = tasks.size(); + checkArgument(ntasks > 0); + List> futures = Lists.newArrayListWithCapacity(ntasks); + BlockingQueue> futureQueue = Queues.newLinkedBlockingQueue(); + + // For efficiency, especially in executors with limited + // parallelism, check to see if previously submitted tasks are + // done before submitting more of them. This interleaving + // plus the exception mechanics account for messiness of main + // loop. + + try { + // Record exceptions so that if we fail to obtain any + // result, we can throw the last exception we got. + ExecutionException ee = null; + long lastTime = timed ? System.nanoTime() : 0; + Iterator> it = tasks.iterator(); + + futures.add(submitAndAddQueueListener(executorService, it.next(), futureQueue)); + --ntasks; + int active = 1; + + for (;;) { + Future f = futureQueue.poll(); + if (f == null) { + if (ntasks > 0) { + --ntasks; + futures.add(submitAndAddQueueListener(executorService, it.next(), futureQueue)); + ++active; + } else if (active == 0) { + break; + } else if (timed) { + f = futureQueue.poll(nanos, TimeUnit.NANOSECONDS); + if (f == null) { + throw new TimeoutException(); + } + long now = System.nanoTime(); + nanos -= now - lastTime; + lastTime = now; + } else { + f = futureQueue.take(); + } + } + if (f != null) { + --active; + try { + return f.get(); + } catch (ExecutionException eex) { + ee = eex; + } catch (RuntimeException rex) { + ee = new ExecutionException(rex); + } + } + } + + if (ee == null) { + ee = new ExecutionException(null); + } + throw ee; + } finally { + for (Future f : futures) { + f.cancel(true); + } + } + } + + /** + * Submits the task and adds a listener that adds the future to {@code queue} when it completes. + */ + private static ListenableFuture submitAndAddQueueListener( + ListeningExecutorService executorService, Callable task, + final BlockingQueue> queue) { + final ListenableFuture future = executorService.submit(task); + future.addListener(new Runnable() { + @Override public void run() { + queue.add(future); + } + }, MoreExecutors.sameThreadExecutor()); + return future; + } +} diff --git a/guava/src/com/google/common/util/concurrent/RateLimiter.java b/guava/src/com/google/common/util/concurrent/RateLimiter.java new file mode 100644 index 0000000..eaf3aa9 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/RateLimiter.java @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Ticker; + +import java.util.concurrent.TimeUnit; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * A rate limiter. Conceptually, a rate limiter distributes permits at a + * configurable rate. Each {@link #acquire()} blocks if necessary until a permit is + * available, and then takes it. Once acquired, permits need not be released. + * + *

    Rate limiters are often used to restrict the rate at which some + * physical or logical resource is accessed. This is in contrast to {@link + * java.util.concurrent.Semaphore} which restricts the number of concurrent + * accesses instead of the rate (note though that concurrency and rate are closely related, + * e.g. see Little's Law). + * + *

    A {@code RateLimiter} is defined primarily by the rate at which permits + * are issued. Absent additional configuration, permits will be distributed at a + * fixed rate, defined in terms of permits per second. Permits will be distributed + * smoothly, with the delay between individual permits being adjusted to ensure + * that the configured rate is maintained. + * + *

    It is possible to configure a {@code RateLimiter} to have a warmup + * period during which time the permits issued each second steadily increases until + * it hits the stable rate. + * + *

    As an example, imagine that we have a list of tasks to execute, but we don't want to + * submit more than 2 per second: + *

      {@code
    + *  final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
    + *  void submitTasks(List tasks, Executor executor) {
    + *    for (Runnable task : tasks) {
    + *      rateLimiter.acquire(); // may wait
    + *      executor.execute(task);
    + *    }
    + *  }
    + *}
    + * + *

    As another example, imagine that we produce a stream of data, and we want to cap it + * at 5kb per second. This could be accomplished by requiring a permit per byte, and specifying + * a rate of 5000 permits per second: + *

      {@code
    + *  final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per second
    + *  void submitPacket(byte[] packet) {
    + *    rateLimiter.acquire(packet.length);
    + *    networkService.send(packet);
    + *  }
    + *}
    + * + *

    It is important to note that the number of permits requested never + * affect the throttling of the request itself (an invocation to {@code acquire(1)} + * and an invocation to {@code acquire(1000)} will result in exactly the same throttling, if any), + * but it affects the throttling of the next request. I.e., if an expensive task + * arrives at an idle RateLimiter, it will be granted immediately, but it is the next + * request that will experience extra throttling, thus paying for the cost of the expensive + * task. + * + *

    Note: {@code RateLimiter} does not provide fairness guarantees. + * + * @author Dimitris Andreou + * @since 13.0 + */ +// TODO(user): switch to nano precision. A natural unit of cost is "bytes", and a micro precision +// would mean a maximum rate of "1MB/s", which might be small in some cases. +@ThreadSafe +@Beta +public abstract class RateLimiter { + /* + * How is the RateLimiter designed, and why? + * + * The primary feature of a RateLimiter is its "stable rate", the maximum rate that + * is should allow at normal conditions. This is enforced by "throttling" incoming + * requests as needed, i.e. compute, for an incoming request, the appropriate throttle time, + * and make the calling thread wait as much. + * + * The simplest way to maintain a rate of QPS is to keep the timestamp of the last + * granted request, and ensure that (1/QPS) seconds have elapsed since then. For example, + * for a rate of QPS=5 (5 tokens per second), if we ensure that a request isn't granted + * earlier than 200ms after the the last one, then we achieve the intended rate. + * If a request comes and the last request was granted only 100ms ago, then we wait for + * another 100ms. At this rate, serving 15 fresh permits (i.e. for an acquire(15) request) + * naturally takes 3 seconds. + * + * It is important to realize that such a RateLimiter has a very superficial memory + * of the past: it only remembers the last request. What if the RateLimiter was unused for + * a long period of time, then a request arrived and was immediately granted? + * This RateLimiter would immediately forget about that past underutilization. This may + * result in either underutilization or overflow, depending on the real world consequences + * of not using the expected rate. + * + * Past underutilization could mean that excess resources are available. Then, the RateLimiter + * should speed up for a while, to take advantage of these resources. This is important + * when the rate is applied to networking (limiting bandwidth), where past underutilization + * typically translates to "almost empty buffers", which can be filled immediately. + * + * On the other hand, past underutilization could mean that "the server responsible for + * handling the request has become less ready for future requests", i.e. its caches become + * stale, and requests become more likely to trigger expensive operations (a more extreme + * case of this example is when a server has just booted, and it is mostly busy with getting + * itself up to speed). + * + * To deal with such scenarios, we add an extra dimension, that of "past underutilization", + * modeled by "storedPermits" variable. This variable is zero when there is no + * underutilization, and it can grow up to maxStoredPermits, for sufficiently large + * underutilization. So, the requested permits, by an invocation acquire(permits), + * are served from: + * - stored permits (if available) + * - fresh permits (for any remaining permits) + * + * How this works is best explained with an example: + * + * For a RateLimiter that produces 1 token per second, every second + * that goes by with the RateLimiter being unused, we increase storedPermits by 1. + * Say we leave the RateLimiter unused for 10 seconds (i.e., we expected a request at time + * X, but we are at time X + 10 seconds before a request actually arrives; this is + * also related to the point made in the last paragraph), thus storedPermits + * becomes 10.0 (assuming maxStoredPermits >= 10.0). At that point, a request of acquire(3) + * arrives. We serve this request out of storedPermits, and reduce that to 7.0 (how this is + * translated to throttling time is discussed later). Immediately after, assume that an + * acquire(10) request arriving. We serve the request partly from storedPermits, + * using all the remaining 7.0 permits, and the remaining 3.0, we serve them by fresh permits + * produced by the rate limiter. + * + * We already know how much time it takes to serve 3 fresh permits: if the rate is + * "1 token per second", then this will take 3 seconds. But what does it mean to serve 7 + * stored permits? As explained above, there is no unique answer. If we are primarily + * interested to deal with underutilization, then we want stored permits to be given out + * /faster/ than fresh ones, because underutilization = free resources for the taking. + * If we are primarily interested to deal with overflow, then stored permits could + * be given out /slower/ than fresh ones. Thus, we require a (different in each case) + * function that translates storedPermits to throtting time. + * + * This role is played by storedPermitsToWaitTime(double storedPermits, double permitsToTake). + * The underlying model is a continuous function mapping storedPermits + * (from 0.0 to maxStoredPermits) onto the 1/rate (i.e. intervals) that is effective at the given + * storedPermits. "storedPermits" essentially measure unused time; we spend unused time + * buying/storing permits. Rate is "permits / time", thus "1 / rate = time / permits". + * Thus, "1/rate" (time / permits) times "permits" gives time, i.e., integrals on this + * function (which is what storedPermitsToWaitTime() computes) correspond to minimum intervals + * between subsequent requests, for the specified number of requested permits. + * + * Here is an example of storedPermitsToWaitTime: + * If storedPermits == 10.0, and we want 3 permits, we take them from storedPermits, + * reducing them to 7.0, and compute the throttling for these as a call to + * storedPermitsToWaitTime(storedPermits = 10.0, permitsToTake = 3.0), which will + * evaluate the integral of the function from 7.0 to 10.0. + * + * Using integrals guarantees that the effect of a single acquire(3) is equivalent + * to { acquire(1); acquire(1); acquire(1); }, or { acquire(2); acquire(1); }, etc, + * since the integral of the function in [7.0, 10.0] is equivalent to the sum of the + * integrals of [7.0, 8.0], [8.0, 9.0], [9.0, 10.0] (and so on), no matter + * what the function is. This guarantees that we handle correctly requests of varying weight + * (permits), /no matter/ what the actual function is - so we can tweak the latter freely. + * (The only requirement, obviously, is that we can compute its integrals). + * + * Note well that if, for this function, we chose a horizontal line, at height of exactly + * (1/QPS), then the effect of the function is non-existent: we serve storedPermits at + * exactly the same cost as fresh ones (1/QPS is the cost for each). We use this trick later. + * + * If we pick a function that goes /below/ that horizontal line, it means that we reduce + * the area of the function, thus time. Thus, the RateLimiter becomes /faster/ after a + * period of underutilization. If, on the other hand, we pick a function that + * goes /above/ that horizontal line, then it means that the area (time) is increased, + * thus storedPermits are more costly than fresh permits, thus the RateLimiter becomes + * /slower/ after a period of underutilization. + * + * Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently + * completely unused, and an expensive acquire(100) request comes. It would be nonsensical + * to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing + * anything? A much better approach is to /allow/ the request right away (as if it was an + * acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, + * we allow starting the task immediately, and postpone by 100 seconds future requests, + * thus we allow for work to get done in the meantime instead of waiting idly. + * + * This has important consequences: it means that the RateLimiter doesn't remember the time + * of the _last_ request, but it remembers the (expected) time of the _next_ request. This + * also enables us to tell immediately (see tryAcquire(timeout)) whether a particular + * timeout is enough to get us to the point of the next scheduling time, since we always + * maintain that. And what we mean by "an unused RateLimiter" is also defined by that + * notion: when we observe that the "expected arrival time of the next request" is actually + * in the past, then the difference (now - past) is the amount of time that the RateLimiter + * was formally unused, and it is that amount of time which we translate to storedPermits. + * (We increase storedPermits with the amount of permits that would have been produced + * in that idle time). So, if rate == 1 permit per second, and arrivals come exactly + * one second after the previous, then storedPermits is _never_ increased -- we would only + * increase it for arrivals _later_ than the expected one second. + */ + + /** + * Creates a {@code RateLimiter} with the specified stable throughput, given as + * "permits per second" (commonly referred to as QPS, queries per second). + * + *

    The returned {@code RateLimiter} ensures that on average no more than {@code + * permitsPerSecond} are issued during any given second, with sustained requests + * being smoothly spread over each second. When the incoming request rate exceeds + * {@code permitsPerSecond} the rate limiter will release one permit every {@code + * (1.0 / permitsPerSecond)} seconds. When the rate limiter is unused, + * bursts of up to {@code permitsPerSecond} permits will be allowed, with subsequent + * requests being smoothly limited at the stable rate of {@code permitsPerSecond}. + * + * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in + * how many permits become available per second. + */ + public static RateLimiter create(double permitsPerSecond) { + return create(SleepingTicker.SYSTEM_TICKER, permitsPerSecond); + } + + @VisibleForTesting + static RateLimiter create(SleepingTicker ticker, double permitsPerSecond) { + RateLimiter rateLimiter = new Bursty(ticker); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } + + /** + * Creates a {@code RateLimiter} with the specified stable throughput, given as + * "permits per second" (commonly referred to as QPS, queries per second), and a + * warmup period, during which the {@code RateLimiter} smoothly ramps up its rate, + * until it reaches its maximum rate at the end of the period (as long as there are enough + * requests to saturate it). Similarly, if the {@code RateLimiter} is left unused for + * a duration of {@code warmupPeriod}, it will gradually return to its "cold" state, + * i.e. it will go through the same warming up process as when it was first created. + * + *

    The returned {@code RateLimiter} is intended for cases where the resource that actually + * fulfils the requests (e.g., a remote server) needs "warmup" time, rather than + * being immediately accessed at the stable (maximum) rate. + * + *

    The returned {@code RateLimiter} starts in a "cold" state (i.e. the warmup period + * will follow), and if it is left unused for long enough, it will return to that state. + * + * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in + * how many permits become available per second + * @param warmupPeriod the duration of the period where the {@code RateLimiter} ramps up its + * rate, before reaching its stable (maximum) rate + * @param unit the time unit of the warmupPeriod argument + */ + // TODO(user): add a burst size of 1-second-worth of permits, as in the metronome? + public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { + return create(SleepingTicker.SYSTEM_TICKER, permitsPerSecond, warmupPeriod, unit); + } + + @VisibleForTesting + static RateLimiter create( + SleepingTicker ticker, double permitsPerSecond, long warmupPeriod, TimeUnit timeUnit) { + RateLimiter rateLimiter = new WarmingUp(ticker, warmupPeriod, timeUnit); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } + + @VisibleForTesting + static RateLimiter createBursty( + SleepingTicker ticker, double permitsPerSecond, int maxBurstSize) { + Bursty rateLimiter = new Bursty(ticker); + rateLimiter.setRate(permitsPerSecond); + rateLimiter.maxPermits = maxBurstSize; + return rateLimiter; + } + + /** + * The underlying timer; used both to measure elapsed time and sleep as necessary. A separate + * object to facilitate testing. + */ + private final SleepingTicker ticker; + + /** + * The timestamp when the RateLimiter was created; used to avoid possible overflow/time-wrapping + * errors. + */ + private final long offsetNanos; + + /** + * The currently stored permits. + */ + double storedPermits; + + /** + * The maximum number of stored permits. + */ + double maxPermits; + + /** + * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits + * per second has a stable interval of 200ms. + */ + double stableIntervalMicros; + + /** + * The time when the next request (no matter its size) will be granted. After granting a request, + * this is pushed further in the future. Large requests push this further than small requests. + */ + private long nextFreeTicketMicros = 0L; // could be either in the past or future + + private RateLimiter(SleepingTicker ticker) { + this.ticker = ticker; + this.offsetNanos = ticker.read(); + } + + /** + * Updates the stable rate of this {@code RateLimiter}, that is, the + * {@code permitsPerSecond} argument provided in the factory method that + * constructed the {@code RateLimiter}. Currently throttled threads will not + * be awakened as a result of this invocation, thus they do not observe the new rate; + * only subsequent requests will. + * + *

    Note though that, since each request repays (by waiting, if necessary) the cost + * of the previous request, this means that the very next request + * after an invocation to {@code setRate} will not be affected by the new rate; + * it will pay the cost of the previous request, which is in terms of the previous rate. + * + *

    The behavior of the {@code RateLimiter} is not modified in any other way, + * e.g. if the {@code RateLimiter} was configured with a warmup period of 20 seconds, + * it still has a warmup period of 20 seconds after this method invocation. + * + * @param permitsPerSecond the new stable rate of this {@code RateLimiter}. + */ + public final synchronized void setRate(double permitsPerSecond) { + Preconditions.checkArgument(permitsPerSecond > 0.0 + && !Double.isNaN(permitsPerSecond), "rate must be positive"); + resync(readSafeMicros()); + double stableIntervalMicros = TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond; + this.stableIntervalMicros = stableIntervalMicros; + doSetRate(permitsPerSecond, stableIntervalMicros); + } + + abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros); + + /** + * Returns the stable rate (as {@code permits per seconds}) with which this + * {@code RateLimiter} is configured with. The initial value of this is the same as + * the {@code permitsPerSecond} argument passed in the factory method that produced + * this {@code RateLimiter}, and it is only updated after invocations + * to {@linkplain #setRate}. + */ + public final synchronized double getRate() { + return TimeUnit.SECONDS.toMicros(1L) / stableIntervalMicros; + } + + /** + * Acquires a permit from this {@code RateLimiter}, blocking until the request can be granted. + * + *

    This method is equivalent to {@code acquire(1)}. + */ + public void acquire() { + acquire(1); + } + + /** + * Acquires the given number of permits from this {@code RateLimiter}, blocking until the + * request be granted. + * + * @param permits the number of permits to acquire + */ + public void acquire(int permits) { + checkPermits(permits); + long microsToWait; + synchronized (this) { + microsToWait = reserveNextTicket(permits, readSafeMicros()); + } + ticker.sleepMicrosUninterruptibly(microsToWait); + } + + /** + * Acquires a permit from this {@code RateLimiter} if it can be obtained + * without exceeding the specified {@code timeout}, or returns {@code false} + * immediately (without waiting) if the permit would not have been granted + * before the timeout expired. + * + *

    This method is equivalent to {@code tryAcquire(1, timeout, unit)}. + * + * @param timeout the maximum time to wait for the permit + * @param unit the time unit of the timeout argument + * @return {@code true} if the permit was acquired, {@code false} otherwise + */ + public boolean tryAcquire(long timeout, TimeUnit unit) { + return tryAcquire(1, timeout, unit); + } + + /** + * Acquires the given number of permits from this {@code RateLimiter} if it can be obtained + * without exceeding the specified {@code timeout}, or returns {@code false} + * immediately (without waiting) if the permits would not have been granted + * before the timeout expired. + * + * @param permits the number of permits to acquire + * @param timeout the maximum time to wait for the permits + * @param unit the time unit of the timeout argument + * @return {@code true} if the permits were acquired, {@code false} otherwise + */ + public boolean tryAcquire(int permits, long timeout, TimeUnit unit) { + checkPermits(permits); + long timeoutMicros = unit.toMicros(timeout); + long microsToWait; + synchronized (this) { + long nowMicros = readSafeMicros(); + if (nextFreeTicketMicros > nowMicros + timeoutMicros) { + return false; + } else { + microsToWait = reserveNextTicket(permits, nowMicros); + } + } + ticker.sleepMicrosUninterruptibly(microsToWait); + return true; + } + + private static void checkPermits(int permits) { + Preconditions.checkArgument(permits > 0, "Requested permits must be positive"); + } + + /** + * Reserves next ticket and returns the wait time that the caller must wait for. + */ + private long reserveNextTicket(double requiredPermits, long nowMicros) { + resync(nowMicros); + long microsToNextFreeTicket = nextFreeTicketMicros - nowMicros; + double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits); + double freshPermits = requiredPermits - storedPermitsToSpend; + + long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + + (long) (freshPermits * stableIntervalMicros); + + this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros; + this.storedPermits -= storedPermitsToSpend; + return microsToNextFreeTicket; + } + + /** + * Translates a specified portion of our currently stored permits which we want to + * spend/acquire, into a throttling time. Conceptually, this evaluates the integral + * of the underlying function we use, for the range of + * [(storedPermits - permitsToTake), storedPermits]. + * + * This always holds: {@code 0 <= permitsToTake <= storedPermits} + */ + abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake); + + private void resync(long nowMicros) { + // if nextFreeTicket is in the past, resync to now + if (nowMicros > nextFreeTicketMicros) { + storedPermits = Math.min(maxPermits, + storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros); + nextFreeTicketMicros = nowMicros; + } + } + + private long readSafeMicros() { + return TimeUnit.NANOSECONDS.toMicros(ticker.read() - offsetNanos); + } + + @Override + public String toString() { + return String.format("RateLimiter[stableRate=%3.1fqps]", 1000000.0 / stableIntervalMicros); + } + + /** + * This implements the following function: + * + * ^ throttling + * | + * 3*stable + / + * interval | /. + * (cold) | / . + * | / . <-- "warmup period" is the area of the trapezoid between + * 2*stable + / . halfPermits and maxPermits + * interval | / . + * | / . + * | / . + * stable +----------/ WARM . } + * interval | . UP . } <-- this rectangle (from 0 to maxPermits, and + * | . PERIOD. } height == stableInterval) defines the cooldown period, + * | . . } and we want cooldownPeriod == warmupPeriod + * |---------------------------------> storedPermits + * (halfPermits) (maxPermits) + * + * Before going into the details of this particular function, let's keep in mind the basics: + * 1) The state of the RateLimiter (storedPermits) is a vertical line in this figure. + * 2) When the RateLimiter is not used, this goes right (up to maxPermits) + * 3) When the RateLimiter is used, this goes left (down to zero), since if we have storedPermits, + * we serve from those first + * 4) When _unused_, we go right at the same speed (rate)! I.e., if our rate is + * 2 permits per second, and 3 unused seconds pass, we will always save 6 permits + * (no matter what our initial position was), up to maxPermits. + * If we invert the rate, we get the "stableInterval" (interval between two requests + * in a perfectly spaced out sequence of requests of the given rate). Thus, if you + * want to see "how much time it will take to go from X storedPermits to X+K storedPermits?", + * the answer is always stableInterval * K. In the same example, for 2 permits per second, + * stableInterval is 500ms. Thus to go from X storedPermits to X+6 storedPermits, we + * require 6 * 500ms = 3 seconds. + * + * In short, the time it takes to move to the right (save K permits) is equal to the + * rectangle of width == K and height == stableInterval. + * 4) When _used_, the time it takes, as explained in the introductory class note, is + * equal to the integral of our function, between X permits and X-K permits, assuming + * we want to spend K saved permits. + * + * In summary, the time it takes to move to the left (spend K permits), is equal to the + * area of the function of width == K. + * + * Let's dive into this function now: + * + * When we have storedPermits <= halfPermits (the left portion of the function), then + * we spend them at the exact same rate that + * fresh permits would be generated anyway (that rate is 1/stableInterval). We size + * this area to be equal to _half_ the specified warmup period. Why we need this? + * And why half? We'll explain shortly below (after explaining the second part). + * + * Stored permits that are beyond halfPermits, are mapped to an ascending line, that goes + * from stableInterval to 3 * stableInterval. The average height for that part is + * 2 * stableInterval, and is sized appropriately to have an area _equal_ to the + * specified warmup period. Thus, by point (4) above, it takes "warmupPeriod" amount of time + * to go from maxPermits to halfPermits. + * + * BUT, by point (3) above, it only takes "warmupPeriod / 2" amount of time to return back + * to maxPermits, from halfPermits! (Because the trapezoid has double the area of the rectangle + * of height stableInterval and equivalent width). We decided that the "cooldown period" + * time should be equivalent to "warmup period", thus a fully saturated RateLimiter + * (with zero stored permits, serving only fresh ones) can go to a fully unsaturated + * (with storedPermits == maxPermits) in the same amount of time it takes for a fully + * unsaturated RateLimiter to return to the stableInterval -- which happens in halfPermits, + * since beyond that point, we use a horizontal line of "stableInterval" height, simulating + * the regular rate. + * + * Thus, we have figured all dimensions of this shape, to give all the desired + * properties: + * - the width is warmupPeriod / stableInterval, to make cooldownPeriod == warmupPeriod + * - the slope starts at the middle, and goes from stableInterval to 3*stableInterval so + * to have halfPermits being spend in double the usual time (half the rate), while their + * respective rate is steadily ramping up + */ + private static class WarmingUp extends RateLimiter { + + final long warmupPeriodMicros; + /** + * The slope of the line from the stable interval (when permits == 0), to the cold interval + * (when permits == maxPermits) + */ + private double slope; + private double halfPermits; + + WarmingUp(SleepingTicker ticker, long warmupPeriod, TimeUnit timeUnit) { + super(ticker); + this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod); + } + + @Override + void doSetRate(double permitsPerSecond, double stableIntervalMicros) { + double oldMaxPermits = maxPermits; + maxPermits = warmupPeriodMicros / stableIntervalMicros; + halfPermits = maxPermits / 2.0; + // Stable interval is x, cold is 3x, so on average it's 2x. Double the time -> halve the rate + double coldIntervalMicros = stableIntervalMicros * 3.0; + slope = (coldIntervalMicros - stableIntervalMicros) / halfPermits; + if (oldMaxPermits == Double.POSITIVE_INFINITY) { + // if we don't special-case this, we would get storedPermits == NaN, below + storedPermits = 0.0; + } else { + storedPermits = (oldMaxPermits == 0.0) + ? maxPermits // initial state is cold + : storedPermits * maxPermits / oldMaxPermits; + } + } + + @Override + long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { + double availablePermitsAboveHalf = storedPermits - halfPermits; + long micros = 0; + // measuring the integral on the right part of the function (the climbing line) + if (availablePermitsAboveHalf > 0.0) { + double permitsAboveHalfToTake = Math.min(availablePermitsAboveHalf, permitsToTake); + micros = (long) (permitsAboveHalfToTake * (permitsToTime(availablePermitsAboveHalf) + + permitsToTime(availablePermitsAboveHalf - permitsAboveHalfToTake)) / 2.0); + permitsToTake -= permitsAboveHalfToTake; + } + // measuring the integral on the left part of the function (the horizontal line) + micros += (stableIntervalMicros * permitsToTake); + return micros; + } + + private double permitsToTime(double permits) { + return stableIntervalMicros + permits * slope; + } + } + + /** + * This implements a trivial function, where storedPermits are translated to + * zero throttling - thus, a client gets an infinite speedup for permits acquired out + * of the storedPermits pool. This is also used for the special case of the "metronome", + * where the width of the function is also zero; maxStoredPermits is zero, thus + * storedPermits and permitsToTake are always zero as well. Such a RateLimiter can + * not save permits when unused, thus all permits it serves are fresh, using the + * designated rate. + */ + private static class Bursty extends RateLimiter { + Bursty(SleepingTicker ticker) { + super(ticker); + } + + @Override + void doSetRate(double permitsPerSecond, double stableIntervalMicros) { + double oldMaxPermits = this.maxPermits; + /* + * We allow the equivalent work of up to one second to be granted with zero waiting, if the + * rate limiter has been unused for as much. This is to avoid potentially producing tiny + * wait interval between subsequent requests for sufficiently large rates, which would + * unnecessarily overconstrain the thread scheduler. + */ + maxPermits = permitsPerSecond; // one second worth of permits + storedPermits = (oldMaxPermits == 0.0) + ? 0.0 // initial state + : storedPermits * maxPermits / oldMaxPermits; + } + + @Override + long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { + return 0L; + } + } + + @VisibleForTesting + static abstract class SleepingTicker extends Ticker { + abstract void sleepMicrosUninterruptibly(long micros); + + static final SleepingTicker SYSTEM_TICKER = new SleepingTicker() { + @Override + public long read() { + return systemTicker().read(); + } + + @Override + public void sleepMicrosUninterruptibly(long micros) { + if (micros > 0) { + Uninterruptibles.sleepUninterruptibly(micros, TimeUnit.MICROSECONDS); + } + } + }; + } +} diff --git a/guava/src/com/google/common/util/concurrent/Service.java b/guava/src/com/google/common/util/concurrent/Service.java new file mode 100644 index 0000000..1c3bed6 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Service.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +/** + * An object with an operational state, plus asynchronous {@link #start()} and {@link #stop()} + * lifecycle methods to transition between states. Example services include webservers, RPC servers + * and timers. + * + *

    The normal lifecycle of a service is: + *

      + *
    • {@linkplain State#NEW NEW} -> + *
    • {@linkplain State#STARTING STARTING} -> + *
    • {@linkplain State#RUNNING RUNNING} -> + *
    • {@linkplain State#STOPPING STOPPING} -> + *
    • {@linkplain State#TERMINATED TERMINATED} + *
    + * + *

    There are deviations from this if there are failures or if {@link Service#stop} is called + * before the {@link Service} reaches the {@linkplain State#RUNNING RUNNING} state. The set of legal + * transitions form a DAG, + * therefore every method of the listener will be called at most once. N.B. The {@link State#FAILED} + * and {@link State#TERMINATED} states are terminal states, once a service enters either of these + * states it cannot ever leave them. + * + *

    Implementors of this interface are strongly encouraged to extend one of the abstract classes + * in this package which implement this interface and make the threading and state management + * easier. + * + * @author Jesse Wilson + * @author Luke Sandberg + * @since 9.0 (in 1.0 as {@code com.google.common.base.Service}) + */ +@Beta +public interface Service { + /** + * If the service state is {@link State#NEW}, this initiates service startup and returns + * immediately. If the service has already been started, this method returns immediately without + * taking action. A stopped service may not be restarted. + * + * @return a future for the startup result, regardless of whether this call initiated startup. + * Calling {@link ListenableFuture#get} will block until the service has finished + * starting, and returns one of {@link State#RUNNING}, {@link State#STOPPING} or + * {@link State#TERMINATED}. If the service fails to start, {@link ListenableFuture#get} + * will throw an {@link ExecutionException}, and the service's state will be + * {@link State#FAILED}. If it has already finished starting, {@link ListenableFuture#get} + * returns immediately. Cancelling this future has no effect on the service. + */ + ListenableFuture start(); + + /** + * Initiates service startup (if necessary), returning once the service has finished starting. + * Unlike calling {@code start().get()}, this method throws no checked exceptions, and it cannot + * be {@linkplain Thread#interrupt interrupted}. + * + * @throws UncheckedExecutionException if startup failed + * @return the state of the service when startup finished. + */ + State startAndWait(); + + /** + * Returns {@code true} if this service is {@linkplain State#RUNNING running}. + */ + boolean isRunning(); + + /** + * Returns the lifecycle state of the service. + */ + State state(); + + /** + * If the service is {@linkplain State#STARTING starting} or {@linkplain State#RUNNING running}, + * this initiates service shutdown and returns immediately. If the service is + * {@linkplain State#NEW new}, it is {@linkplain State#TERMINATED terminated} without having been + * started nor stopped. If the service has already been stopped, this method returns immediately + * without taking action. + * + * @return a future for the shutdown result, regardless of whether this call initiated shutdown. + * Calling {@link ListenableFuture#get} will block until the service has finished shutting + * down, and either returns {@link State#TERMINATED} or throws an + * {@link ExecutionException}. If it has already finished stopping, + * {@link ListenableFuture#get} returns immediately. Cancelling this future has no effect + * on the service. + */ + ListenableFuture stop(); + + /** + * Initiates service shutdown (if necessary), returning once the service has finished stopping. If + * this is {@link State#STARTING}, startup will be cancelled. If this is {@link State#NEW}, it is + * {@link State#TERMINATED terminated} without having been started nor stopped. Unlike calling + * {@code stop().get()}, this method throws no checked exceptions. + * + * @throws UncheckedExecutionException if the service has failed or fails during shutdown + * @return the state of the service when shutdown finished. + */ + State stopAndWait(); + + /** + * Registers a {@link Listener} to be {@linkplain Executor#execute executed} on the given + * executor. The listener will have the corresponding transition method called whenever the + * service changes state. The listener will not have previous state changes replayed, so it is + * suggested that listeners are added before the service starts. + * + *

    There is no guaranteed ordering of execution of listeners, but any listener added through + * this method is guaranteed to be called whenever there is a state change. + * + *

    Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown + * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception + * thrown by {@linkplain MoreExecutors#sameThreadExecutor inline execution}) will be caught and + * logged. + * + * @param listener the listener to run when the service changes state is complete + * @param executor the executor in which the the listeners callback methods will be run. For fast, + * lightweight listeners that would be safe to execute in any thread, consider + * {@link MoreExecutors#sameThreadExecutor}. + * @since 13.0 + */ + void addListener(Listener listener, Executor executor); + + /** + * The lifecycle states of a service. + * + * @since 9.0 (in 1.0 as {@code com.google.common.base.Service.State}) + */ + @Beta // should come out of Beta when Service does + enum State { + /** + * A service in this state is inactive. It does minimal work and consumes + * minimal resources. + */ + NEW, + + /** + * A service in this state is transitioning to {@link #RUNNING}. + */ + STARTING, + + /** + * A service in this state is operational. + */ + RUNNING, + + /** + * A service in this state is transitioning to {@link #TERMINATED}. + */ + STOPPING, + + /** + * A service in this state has completed execution normally. It does minimal work and consumes + * minimal resources. + */ + TERMINATED, + + /** + * A service in this state has encountered a problem and may not be operational. It cannot be + * started nor stopped. + */ + FAILED + } + + /** + * A listener for the various state changes that a {@link Service} goes through in its lifecycle. + * + * @author Luke Sandberg + * @since 13.0 + */ + @Beta // should come out of Beta when Service does + interface Listener { + /** + * Called when the service transitions from {@linkplain State#NEW NEW} to + * {@linkplain State#STARTING STARTING}. This occurs when {@link Service#start} or + * {@link Service#startAndWait} is called the first time. + */ + void starting(); + + /** + * Called when the service transitions from {@linkplain State#STARTING STARTING} to + * {@linkplain State#RUNNING RUNNING}. This occurs when a service has successfully started. + */ + void running(); + + /** + * Called when the service transitions to the {@linkplain State#STOPPING STOPPING} state. The + * only valid values for {@code from} are {@linkplain State#STARTING STARTING} or + * {@linkplain State#RUNNING RUNNING}. This occurs when {@link Service#stop} is called. + * + * @param from The previous state that is being transitioned from. + */ + void stopping(State from); + + /** + * Called when the service transitions to the {@linkplain State#TERMINATED TERMINATED} state. + * The {@linkplain State#TERMINATED TERMINATED} state is a terminal state in the transition + * diagram. Therefore, if this method is called, no other methods will be called on the + * {@link Listener}. + * + * @param from The previous state that is being transitioned from. The only valid values for + * this are {@linkplain State#NEW NEW}, {@linkplain State#RUNNING RUNNING} or + * {@linkplain State#STOPPING STOPPING}. + */ + void terminated(State from); + + /** + * Called when the service transitions to the {@linkplain State#FAILED FAILED} state. The + * {@linkplain State#FAILED FAILED} state is a terminal state in the transition diagram. + * Therefore, if this method is called, no other methods will be called on the {@link Listener}. + * + * @param from The previous state that is being transitioned from. Failure can occur in any + * state with the exception of {@linkplain State#NEW NEW} or + * {@linkplain State#TERMINATED TERMINATED}. + * @param failure The exception that caused the failure. + */ + void failed(State from, Throwable failure); + } +} diff --git a/guava/src/com/google/common/util/concurrent/SettableFuture.java b/guava/src/com/google/common/util/concurrent/SettableFuture.java new file mode 100644 index 0000000..23e14f9 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/SettableFuture.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import javax.annotation.Nullable; + +/** + * A {@link ListenableFuture} whose result may be set by a {@link #set(Object)} + * or {@link #setException(Throwable)} call. It may also be cancelled. + * + * @author Sven Mawson + * @since 9.0 (in 1.0 as {@code ValueFuture}) + */ +public final class SettableFuture extends AbstractFuture { + + /** + * Creates a new {@code SettableFuture} in the default state. + */ + public static SettableFuture create() { + return new SettableFuture(); + } + + /** + * Explicit private constructor, use the {@link #create} factory method to + * create instances of {@code SettableFuture}. + */ + private SettableFuture() {} + + /** + * Sets the value of this future. This method will return {@code true} if + * the value was successfully set, or {@code false} if the future has already + * been set or cancelled. + * + * @param value the value the future should hold. + * @return true if the value was successfully set. + */ + @Override + public boolean set(@Nullable V value) { + return super.set(value); + } + + /** + * Sets the future to having failed with the given exception. This exception + * will be wrapped in an {@code ExecutionException} and thrown from the {@code + * get} methods. This method will return {@code true} if the exception was + * successfully set, or {@code false} if the future has already been set or + * cancelled. + * + * @param throwable the exception the future should hold. + * @return true if the exception was successfully set. + */ + @Override + public boolean setException(Throwable throwable) { + return super.setException(throwable); + } +} diff --git a/guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java b/guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java new file mode 100644 index 0000000..0f7c3e4 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ObjectArrays; +import com.google.common.collect.Sets; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A TimeLimiter that runs method calls in the background using an + * {@link ExecutorService}. If the time limit expires for a given method call, + * the thread running the call will be interrupted. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@Beta +public final class SimpleTimeLimiter implements TimeLimiter { + + private final ExecutorService executor; + + /** + * Constructs a TimeLimiter instance using the given executor service to + * execute proxied method calls. + *

    + * Warning: using a bounded executor + * may be counterproductive! If the thread pool fills up, any time callers + * spend waiting for a thread may count toward their time limit, and in + * this case the call may even time out before the target method is ever + * invoked. + * + * @param executor the ExecutorService that will execute the method calls on + * the target objects; for example, a {@link + * Executors#newCachedThreadPool()}. + */ + public SimpleTimeLimiter(ExecutorService executor) { + this.executor = checkNotNull(executor); + } + + /** + * Constructs a TimeLimiter instance using a {@link + * Executors#newCachedThreadPool()} to execute proxied method calls. + * + *

    Warning: using a bounded executor may be counterproductive! If + * the thread pool fills up, any time callers spend waiting for a thread may + * count toward their time limit, and in this case the call may even time out + * before the target method is ever invoked. + */ + public SimpleTimeLimiter() { + this(Executors.newCachedThreadPool()); + } + + @Override + public T newProxy(final T target, Class interfaceType, + final long timeoutDuration, final TimeUnit timeoutUnit) { + checkNotNull(target); + checkNotNull(interfaceType); + checkNotNull(timeoutUnit); + checkArgument(timeoutDuration > 0, "bad timeout: " + timeoutDuration); + checkArgument(interfaceType.isInterface(), + "interfaceType must be an interface type"); + + final Set interruptibleMethods + = findInterruptibleMethods(interfaceType); + + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object obj, final Method method, final Object[] args) + throws Throwable { + Callable callable = new Callable() { + @Override + public Object call() throws Exception { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throwCause(e, false); + throw new AssertionError("can't get here"); + } + } + }; + return callWithTimeout(callable, timeoutDuration, timeoutUnit, + interruptibleMethods.contains(method)); + } + }; + return newProxy(interfaceType, handler); + } + + // TODO: should this actually throw only ExecutionException? + @Override + public T callWithTimeout(Callable callable, long timeoutDuration, + TimeUnit timeoutUnit, boolean amInterruptible) throws Exception { + checkNotNull(callable); + checkNotNull(timeoutUnit); + checkArgument(timeoutDuration > 0, "timeout must be positive: %s", + timeoutDuration); + Future future = executor.submit(callable); + try { + if (amInterruptible) { + try { + return future.get(timeoutDuration, timeoutUnit); + } catch (InterruptedException e) { + future.cancel(true); + throw e; + } + } else { + return Uninterruptibles.getUninterruptibly(future, + timeoutDuration, timeoutUnit); + } + } catch (ExecutionException e) { + throw throwCause(e, true); + } catch (TimeoutException e) { + future.cancel(true); + throw new UncheckedTimeoutException(e); + } + } + + private static Exception throwCause(Exception e, boolean combineStackTraces) + throws Exception { + Throwable cause = e.getCause(); + if (cause == null) { + throw e; + } + if (combineStackTraces) { + StackTraceElement[] combined = ObjectArrays.concat(cause.getStackTrace(), + e.getStackTrace(), StackTraceElement.class); + cause.setStackTrace(combined); + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + // The cause is a weird kind of Throwable, so throw the outer exception. + throw e; + } + + private static Set findInterruptibleMethods(Class interfaceType) { + Set set = Sets.newHashSet(); + for (Method m : interfaceType.getMethods()) { + if (declaresInterruptedEx(m)) { + set.add(m); + } + } + return set; + } + + private static boolean declaresInterruptedEx(Method method) { + for (Class exType : method.getExceptionTypes()) { + // debate: == or isAssignableFrom? + if (exType == InterruptedException.class) { + return true; + } + } + return false; + } + + // TODO: replace with version in common.reflect if and when it's open-sourced + private static T newProxy( + Class interfaceType, InvocationHandler handler) { + Object object = Proxy.newProxyInstance(interfaceType.getClassLoader(), + new Class[] { interfaceType }, handler); + return interfaceType.cast(object); + } +} diff --git a/guava/src/com/google/common/util/concurrent/Striped.java b/guava/src/com/google/common/util/concurrent/Striped.java new file mode 100644 index 0000000..3c426f0 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Striped.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.collect.Iterables; +import com.google.common.collect.MapMaker; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * A striped {@code Lock/Semaphore/ReadWriteLock}. This offers the underlying lock striping + * similar to that of {@code ConcurrentHashMap} in a reusable form, and extends it for + * semaphores and read-write locks. Conceptually, lock striping is the technique of dividing a lock + * into many stripes, increasing the granularity of a single lock and allowing independent + * operations to lock different stripes and proceed concurrently, instead of creating contention + * for a single lock. + * + *

    The guarantee provided by this class is that equal keys lead to the same lock (or semaphore), + * i.e. {@code if (key1.equals(key2))} then {@code striped.get(key1) == striped.get(key2)} + * (assuming {@link Object#hashCode()} is correctly implemented for the keys). Note + * that if {@code key1} is not equal to {@code key2}, it is not + * guaranteed that {@code striped.get(key1) != striped.get(key2)}; the elements might nevertheless + * be mapped to the same lock. The lower the number of stripes, the higher the probability of this + * happening. + * + *

    There are three flavors of this class: {@code Striped}, {@code Striped}, + * and {@code Striped}. For each type, two implementations are offered: + * {@linkplain #lock(int) strong} and {@linkplain #lazyWeakLock(int) weak} + * {@code Striped}, {@linkplain #semaphore(int, int) strong} and {@linkplain + * #lazyWeakSemaphore(int, int) weak} {@code Striped}, and {@linkplain + * #readWriteLock(int) strong} and {@linkplain #lazyWeakReadWriteLock(int) weak} + * {@code Striped}. Strong means that all stripes (locks/semaphores) are + * initialized eagerly, and are not reclaimed unless {@code Striped} itself is reclaimable. + * Weak means that locks/semaphores are created lazily, and they are allowed to be reclaimed + * if nobody is holding on to them. This is useful, for example, if one wants to create a {@code + * Striped} of many locks, but worries that in most cases only a small portion of these + * would be in use. + * + *

    Prior to this class, one might be tempted to use {@code Map}, where {@code K} + * represents the task. This maximizes concurrency by having each unique key mapped to a unique + * lock, but also maximizes memory footprint. On the other extreme, one could use a single lock + * for all tasks, which minimizes memory footprint but also minimizes concurrency. Instead of + * choosing either of these extremes, {@code Striped} allows the user to trade between required + * concurrency and memory footprint. For example, if a set of tasks are CPU-bound, one could easily + * create a very compact {@code Striped} of {@code availableProcessors() * 4} stripes, + * instead of possibly thousands of locks which could be created in a {@code Map} + * structure. + * + * @author Dimitris Andreou + * @since 13.0 + */ +@Beta +public abstract class Striped { + private Striped() {} + + /** + * Returns the stripe that corresponds to the passed key. It is always guaranteed that if + * {@code key1.equals(key2)}, then {@code get(key1) == get(key2)}. + * + * @param key an arbitrary, non-null key + * @return the stripe that the passed key corresponds to + */ + public abstract L get(Object key); + + /** + * Returns the stripe at the specified index. Valid indexes are 0, inclusively, to + * {@code size()}, exclusively. + * + * @param index the index of the stripe to return; must be in {@code [0...size())} + * @return the stripe at the specified index + */ + public abstract L getAt(int index); + + /** + * Returns the index to which the given key is mapped, so that getAt(indexFor(key)) == get(key). + */ + abstract int indexFor(Object key); + + /** + * Returns the total number of stripes in this instance. + */ + public abstract int size(); + + /** + * Returns the stripes that correspond to the passed objects, in ascending (as per + * {@link #getAt(int)}) order. Thus, threads that use the stripes in the order returned + * by this method are guaranteed to not deadlock each other. + * + *

    It should be noted that using a {@code Striped} with relatively few stripes, and + * {@code bulkGet(keys)} with a relative large number of keys can cause an excessive number + * of shared stripes (much like the birthday paradox, where much fewer than anticipated birthdays + * are needed for a pair of them to match). Please consider carefully the implications of the + * number of stripes, the intended concurrency level, and the typical number of keys used in a + * {@code bulkGet(keys)} operation. See Balls + * in Bins model for mathematical formulas that can be used to estimate the probability of + * collisions. + * + * @param keys arbitrary non-null keys + * @return the stripes corresponding to the objects (one per each object, derived by delegating + * to {@link #get(Object)}; may contain duplicates), in an increasing index order. + */ + public Iterable bulkGet(Iterable keys) { + // Initially using the array to store the keys, then reusing it to store the respective L's + final Object[] array = Iterables.toArray(keys, Object.class); + int[] stripes = new int[array.length]; + for (int i = 0; i < array.length; i++) { + stripes[i] = indexFor(array[i]); + } + Arrays.sort(stripes); + for (int i = 0; i < array.length; i++) { + array[i] = getAt(stripes[i]); + } + /* + * Note that the returned Iterable holds references to the returned stripes, to avoid + * error-prone code like: + * + * Striped stripedLock = Striped.lazyWeakXXX(...)' + * Iterable locks = stripedLock.bulkGet(keys); + * for (Lock lock : locks) { + * lock.lock(); + * } + * operation(); + * for (Lock lock : locks) { + * lock.unlock(); + * } + * + * If we only held the int[] stripes, translating it on the fly to L's, the original locks + * might be garbage collected after locking them, ending up in a huge mess. + */ + @SuppressWarnings("unchecked") // we carefully replaced all keys with their respective L's + List asList = (List) Arrays.asList(array); + return Collections.unmodifiableList(asList); + } + + // Static factories + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced locks, with the + * specified fairness. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lock(int stripes) { + return new CompactStriped(stripes, new Supplier() { + public Lock get() { + return new PaddedLock(); + } + }); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced locks, with the + * specified fairness. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lazyWeakLock(int stripes) { + return new LazyStriped(stripes, new Supplier() { + public Lock get() { + return new ReentrantLock(false); + } + }); + } + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced semaphores, + * with the specified number of permits and fairness. + * + * @param stripes the minimum number of stripes (semaphores) required + * @param permits the number of permits in each semaphore + * @return a new {@code Striped} + */ + public static Striped semaphore(int stripes, final int permits) { + return new CompactStriped(stripes, new Supplier() { + public Semaphore get() { + return new PaddedSemaphore(permits); + } + }); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced semaphores, + * with the specified number of permits and fairness. + * + * @param stripes the minimum number of stripes (semaphores) required + * @param permits the number of permits in each semaphore + * @return a new {@code Striped} + */ + public static Striped lazyWeakSemaphore(int stripes, final int permits) { + return new LazyStriped(stripes, new Supplier() { + public Semaphore get() { + return new Semaphore(permits, false); + } + }); + } + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced + * read-write locks, with the specified fairness. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped readWriteLock(int stripes) { + return new CompactStriped(stripes, READ_WRITE_LOCK_SUPPLIER); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced + * read-write locks, with the specified fairness. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lazyWeakReadWriteLock(int stripes) { + return new LazyStriped(stripes, READ_WRITE_LOCK_SUPPLIER); + } + + // ReentrantReadWriteLock is large enough to make padding probably unnecessary + private static final Supplier READ_WRITE_LOCK_SUPPLIER = + new Supplier() { + public ReadWriteLock get() { + return new ReentrantReadWriteLock(); + } + }; + + private abstract static class PowerOfTwoStriped extends Striped { + /** Capacity (power of two) minus one, for fast mod evaluation */ + final int mask; + + PowerOfTwoStriped(int stripes) { + Preconditions.checkArgument(stripes > 0, "Stripes must be positive"); + this.mask = stripes > Ints.MAX_POWER_OF_TWO ? ALL_SET : ceilToPowerOfTwo(stripes) - 1; + } + + @Override final int indexFor(Object key) { + int hash = smear(key.hashCode()); + return hash & mask; + } + + @Override public final L get(Object key) { + return getAt(indexFor(key)); + } + } + + /** + * Implementation of Striped where 2^k stripes are represented as an array of the same length, + * eagerly initialized. + */ + private static class CompactStriped extends PowerOfTwoStriped { + /** Size is a power of two. */ + private final Object[] array; + + private CompactStriped(int stripes, Supplier supplier) { + super(stripes); + Preconditions.checkArgument(stripes <= Ints.MAX_POWER_OF_TWO, "Stripes must be <= 2^30)"); + + this.array = new Object[mask + 1]; + for (int i = 0; i < array.length; i++) { + array[i] = supplier.get(); + } + } + + @SuppressWarnings("unchecked") // we only put L's in the array + @Override public L getAt(int index) { + return (L) array[index]; + } + + @Override public int size() { + return array.length; + } + } + + /** + * Implementation of Striped where up to 2^k stripes can be represented, using a Cache + * where the key domain is [0..2^k). To map a user key into a stripe, we take a k-bit slice of the + * user key's (smeared) hashCode(). The stripes are lazily initialized and are weakly referenced. + */ + private static class LazyStriped extends PowerOfTwoStriped { + final ConcurrentMap cache; + final int size; + + LazyStriped(int stripes, Supplier supplier) { + super(stripes); + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + this.cache = new MapMaker().weakValues().makeComputingMap(Functions.forSupplier(supplier)); + } + + @Override public L getAt(int index) { + Preconditions.checkElementIndex(index, size()); + return cache.get(index); + } + + @Override public int size() { + return size; + } + } + + /** + * A bit mask were all bits are set. + */ + private static final int ALL_SET = ~0; + + private static int ceilToPowerOfTwo(int x) { + return 1 << IntMath.log2(x, RoundingMode.CEILING); + } + + /* + * This method was written by Doug Lea with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + * + * As of 2010/06/11, this method is identical to the (package private) hash + * method in OpenJDK 7's java.util.HashMap class. + */ + // Copied from java/com/google/common/collect/Hashing.java + private static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } + + private static class PaddedLock extends ReentrantLock { + /* + * Padding from 40 into 64 bytes, same size as cache line. Might be beneficial to add + * a fourth long here, to minimize chance of interference between consecutive locks, + * but I couldn't observe any benefit from that. + */ + @SuppressWarnings("unused") + long q1, q2, q3; + + PaddedLock() { + super(false); + } + } + + private static class PaddedSemaphore extends Semaphore { + // See PaddedReentrantLock comment + @SuppressWarnings("unused") + long q1, q2, q3; + + PaddedSemaphore(int permits) { + super(permits, false); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/ThreadFactoryBuilder.java b/guava/src/com/google/common/util/concurrent/ThreadFactoryBuilder.java new file mode 100644 index 0000000..167ad11 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/ThreadFactoryBuilder.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A ThreadFactory builder, providing any combination of these features: + *

      + *
    • whether threads should be marked as {@linkplain Thread#setDaemon daemon} + * threads + *
    • a {@linkplain ThreadFactoryBuilder#setNameFormat naming format} + *
    • a {@linkplain Thread#setPriority thread priority} + *
    • an {@linkplain Thread#setUncaughtExceptionHandler uncaught exception + * handler} + *
    • a {@linkplain ThreadFactory#newThread backing thread factory} + *
    + * If no backing thread factory is provided, a default backing thread factory is + * used as if by calling {@code setThreadFactory(}{@link + * Executors#defaultThreadFactory()}{@code )}. + * + * @author Kurt Alfred Kluever + * @since 4.0 + */ +public final class ThreadFactoryBuilder { + private String nameFormat = null; + private Boolean daemon = null; + private Integer priority = null; + private UncaughtExceptionHandler uncaughtExceptionHandler = null; + private ThreadFactory backingThreadFactory = null; + + /** + * Creates a new {@link ThreadFactory} builder. + */ + public ThreadFactoryBuilder() {} + + /** + * Sets the naming format to use when naming threads ({@link Thread#setName}) + * which are created with this ThreadFactory. + * + * @param nameFormat a {@link String#format(String, Object...)}-compatible + * format String, to which a unique integer (0, 1, etc.) will be supplied + * as the single parameter. This integer will be unique to the built + * instance of the ThreadFactory and will be assigned sequentially. + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setNameFormat(String nameFormat) { + String.format(nameFormat, 0); // fail fast if the format is bad or null + this.nameFormat = nameFormat; + return this; + } + + /** + * Sets daemon or not for new threads created with this ThreadFactory. + * + * @param daemon whether or not new Threads created with this ThreadFactory + * will be daemon threads + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setDaemon(boolean daemon) { + this.daemon = daemon; + return this; + } + + /** + * Sets the priority for new threads created with this ThreadFactory. + * + * @param priority the priority for new Threads created with this + * ThreadFactory + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setPriority(int priority) { + // Thread#setPriority() already checks for validity. These error messages + // are nicer though and will fail-fast. + checkArgument(priority >= Thread.MIN_PRIORITY, + "Thread priority (%s) must be >= %s", priority, Thread.MIN_PRIORITY); + checkArgument(priority <= Thread.MAX_PRIORITY, + "Thread priority (%s) must be <= %s", priority, Thread.MAX_PRIORITY); + this.priority = priority; + return this; + } + + /** + * Sets the {@link UncaughtExceptionHandler} for new threads created with this + * ThreadFactory. + * + * @param uncaughtExceptionHandler the uncaught exception handler for new + * Threads created with this ThreadFactory + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setUncaughtExceptionHandler( + UncaughtExceptionHandler uncaughtExceptionHandler) { + this.uncaughtExceptionHandler = checkNotNull(uncaughtExceptionHandler); + return this; + } + + /** + * Sets the backing {@link ThreadFactory} for new threads created with this + * ThreadFactory. Threads will be created by invoking #newThread(Runnable) on + * this backing {@link ThreadFactory}. + * + * @param backingThreadFactory the backing {@link ThreadFactory} which will + * be delegated to during thread creation. + * @return this for the builder pattern + * + * @see MoreExecutors + */ + public ThreadFactoryBuilder setThreadFactory( + ThreadFactory backingThreadFactory) { + this.backingThreadFactory = checkNotNull(backingThreadFactory); + return this; + } + + /** + * Returns a new thread factory using the options supplied during the building + * process. After building, it is still possible to change the options used to + * build the ThreadFactory and/or build again. State is not shared amongst + * built instances. + * + * @return the fully constructed {@link ThreadFactory} + */ + public ThreadFactory build() { + return build(this); + } + + private static ThreadFactory build(ThreadFactoryBuilder builder) { + final String nameFormat = builder.nameFormat; + final Boolean daemon = builder.daemon; + final Integer priority = builder.priority; + final UncaughtExceptionHandler uncaughtExceptionHandler = + builder.uncaughtExceptionHandler; + final ThreadFactory backingThreadFactory = + (builder.backingThreadFactory != null) + ? builder.backingThreadFactory + : Executors.defaultThreadFactory(); + final AtomicLong count = (nameFormat != null) ? new AtomicLong(0) : null; + return new ThreadFactory() { + @Override public Thread newThread(Runnable runnable) { + Thread thread = backingThreadFactory.newThread(runnable); + if (nameFormat != null) { + thread.setName(String.format(nameFormat, count.getAndIncrement())); + } + if (daemon != null) { + thread.setDaemon(daemon); + } + if (priority != null) { + thread.setPriority(priority); + } + if (uncaughtExceptionHandler != null) { + thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); + } + return thread; + } + }; + } +} diff --git a/guava/src/com/google/common/util/concurrent/TimeLimiter.java b/guava/src/com/google/common/util/concurrent/TimeLimiter.java new file mode 100644 index 0000000..a9938de --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/TimeLimiter.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * Produces proxies that impose a time limit on method + * calls to the proxied object. For example, to return the value of + * {@code target.someMethod()}, but substitute {@code DEFAULT_VALUE} if this + * method call takes over 50 ms, you can use this code: + *
    + *   TimeLimiter limiter = . . .;
    + *   TargetType proxy = limiter.newProxy(
    + *       target, TargetType.class, 50, TimeUnit.MILLISECONDS);
    + *   try {
    + *     return proxy.someMethod();
    + *   } catch (UncheckedTimeoutException e) {
    + *     return DEFAULT_VALUE;
    + *   }
    + * 
    + * Please see {@code SimpleTimeLimiterTest} for more usage examples. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@Beta +public interface TimeLimiter { + + /** + * Returns an instance of {@code interfaceType} that delegates all method + * calls to the {@code target} object, enforcing the specified time limit on + * each call. This time-limited delegation is also performed for calls to + * {@link Object#equals}, {@link Object#hashCode}, and + * {@link Object#toString}. + *

    + * If the target method call finishes before the limit is reached, the return + * value or exception is propagated to the caller exactly as-is. If, on the + * other hand, the time limit is reached, the proxy will attempt to abort the + * call to the target, and will throw an {@link UncheckedTimeoutException} to + * the caller. + *

    + * It is important to note that the primary purpose of the proxy object is to + * return control to the caller when the timeout elapses; aborting the target + * method call is of secondary concern. The particular nature and strength + * of the guarantees made by the proxy is implementation-dependent. However, + * it is important that each of the methods on the target object behaves + * appropriately when its thread is interrupted. + * + * @param target the object to proxy + * @param interfaceType the interface you wish the returned proxy to + * implement + * @param timeoutDuration with timeoutUnit, the maximum length of time that + * callers are willing to wait on each method call to the proxy + * @param timeoutUnit with timeoutDuration, the maximum length of time that + * callers are willing to wait on each method call to the proxy + * @return a time-limiting proxy + * @throws IllegalArgumentException if {@code interfaceType} is a regular + * class, enum, or annotation type, rather than an interface + */ + T newProxy(T target, Class interfaceType, + long timeoutDuration, TimeUnit timeoutUnit); + + /** + * Invokes a specified Callable, timing out after the specified time limit. + * If the target method call finished before the limit is reached, the return + * value or exception is propagated to the caller exactly as-is. If, on the + * other hand, the time limit is reached, we attempt to abort the call to the + * target, and throw an {@link UncheckedTimeoutException} to the caller. + *

    + * Warning: The future of this method is in doubt. It may be nuked, or + * changed significantly. + * + * @param callable the Callable to execute + * @param timeoutDuration with timeoutUnit, the maximum length of time to wait + * @param timeoutUnit with timeoutDuration, the maximum length of time to wait + * @param interruptible whether to respond to thread interruption by aborting + * the operation and throwing InterruptedException; if false, the + * operation is allowed to complete or time out, and the current thread's + * interrupt status is re-asserted. + * @return the result returned by the Callable + * @throws InterruptedException if {@code interruptible} is true and our + * thread is interrupted during execution + * @throws UncheckedTimeoutException if the time limit is reached + * @throws Exception + */ + T callWithTimeout(Callable callable, long timeoutDuration, + TimeUnit timeoutUnit, boolean interruptible) throws Exception; +} diff --git a/guava/src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java b/guava/src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java new file mode 100644 index 0000000..bdff7db --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/UncaughtExceptionHandlers.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static java.util.logging.Level.SEVERE; + +import com.google.common.annotations.VisibleForTesting; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.logging.Logger; + +/** + * Factories for {@link UncaughtExceptionHandler} instances. + * + * @author Gregory Kick + * @since 8.0 + */ +public final class UncaughtExceptionHandlers { + private UncaughtExceptionHandlers() {} + + /** + * Returns an exception handler that exits the system. This is particularly useful for the main + * thread, which may start up other, non-daemon threads, but fail to fully initialize the + * application successfully. + * + *

    Example usage: + *

    public static void main(String[] args) {
    +   *   Thread.currentThread().setUncaughtExceptionHandler(UncaughtExceptionHandlers.systemExit());
    +   *   ...
    +   * 
    + */ + public static UncaughtExceptionHandler systemExit() { + return new Exiter(Runtime.getRuntime()); + } + + @VisibleForTesting static final class Exiter implements UncaughtExceptionHandler { + private static final Logger logger = Logger.getLogger(Exiter.class.getName()); + + private final Runtime runtime; + + Exiter(Runtime runtime) { + this.runtime = runtime; + } + + @Override public void uncaughtException(Thread t, Throwable e) { + // cannot use FormattingLogger due to a dependency loop + logger.log(SEVERE, String.format("Caught an exception in %s. Shutting down.", t), e); + runtime.exit(1); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/UncheckedExecutionException.java b/guava/src/com/google/common/util/concurrent/UncheckedExecutionException.java new file mode 100644 index 0000000..ad84535 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/UncheckedExecutionException.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Unchecked variant of {@link java.util.concurrent.ExecutionException}. As with + * {@code ExecutionException}, the exception's {@linkplain #getCause() cause} + * comes from a failed task, possibly run in another thread. + * + *

    {@code UncheckedExecutionException} is intended as an alternative to + * {@code ExecutionException} when the exception thrown by a task is an + * unchecked exception. However, it may also wrap a checked exception in some + * cases. + * + *

    When wrapping an {@code Error} from another thread, prefer {@link + * ExecutionError}. When wrapping a checked exception, prefer {@code + * ExecutionException}. + * + * @author Charles Fry + * @since 10.0 + */ +@Beta +@GwtCompatible +public class UncheckedExecutionException extends RuntimeException { + /** + * Creates a new instance with {@code null} as its detail message. + */ + protected UncheckedExecutionException() {} + + /** + * Creates a new instance with the given detail message. + */ + protected UncheckedExecutionException(String message) { + super(message); + } + + /** + * Creates a new instance with the given detail message and cause. + */ + public UncheckedExecutionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance with the given cause. + */ + public UncheckedExecutionException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/util/concurrent/UncheckedTimeoutException.java b/guava/src/com/google/common/util/concurrent/UncheckedTimeoutException.java new file mode 100644 index 0000000..d821c84 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/UncheckedTimeoutException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +/** + * Unchecked version of {@link java.util.concurrent.TimeoutException}. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public class UncheckedTimeoutException extends RuntimeException { + public UncheckedTimeoutException() {} + + public UncheckedTimeoutException(String message) { + super(message); + } + + public UncheckedTimeoutException(Throwable cause) { + super(cause); + } + + public UncheckedTimeoutException(String message, Throwable cause) { + super(message, cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/guava/src/com/google/common/util/concurrent/Uninterruptibles.java b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java new file mode 100644 index 0000000..89f30b8 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Utilities for treating interruptible operations as uninterruptible. + * In all cases, if a thread is interrupted during such a call, the call + * continues to block until the result is available or the timeout elapses, + * and only then re-interrupts the thread. + * + * @author Anthony Zana + * @since 10.0 + */ +@Beta +public final class Uninterruptibles { + + // Implementation Note: As of 3-7-11, the logic for each blocking/timeout + // methods is identical, save for method being invoked. + + /** + * Invokes {@code latch.}{@link CountDownLatch#await() await()} + * uninterruptibly. + */ + public static void awaitUninterruptibly(CountDownLatch latch) { + boolean interrupted = false; + try { + while (true) { + try { + latch.await(); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes + * {@code latch.}{@link CountDownLatch#await(long, TimeUnit) + * await(timeout, unit)} uninterruptibly. + */ + public static boolean awaitUninterruptibly(CountDownLatch latch, + long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // CountDownLatch treats negative timeouts just like zero. + return latch.await(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code toJoin.}{@link Thread#join() join()} uninterruptibly. + */ + public static void joinUninterruptibly(Thread toJoin) { + boolean interrupted = false; + try { + while (true) { + try { + toJoin.join(); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code future.}{@link Future#get() get()} uninterruptibly. + * To get uninterruptibility and remove checked exceptions, see + * {@link Futures#getUnchecked}. + * + *

    If instead, you wish to treat {@link InterruptedException} uniformly + * with other exceptions, see {@link Futures#get(Future, Class) Futures.get} + * or {@link Futures#makeChecked}. + */ + public static V getUninterruptibly(Future future) + throws ExecutionException { + boolean interrupted = false; + try { + while (true) { + try { + return future.get(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes + * {@code future.}{@link Future#get(long, TimeUnit) get(timeout, unit)} + * uninterruptibly. + * + *

    If instead, you wish to treat {@link InterruptedException} uniformly + * with other exceptions, see {@link Futures#get(Future, Class) Futures.get} + * or {@link Futures#makeChecked}. + */ + public static V getUninterruptibly( + Future future, long timeout, TimeUnit unit) + throws ExecutionException, TimeoutException { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // Future treats negative timeouts just like zero. + return future.get(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes + * {@code unit.}{@link TimeUnit#timedJoin(Thread, long) + * timedJoin(toJoin, timeout)} uninterruptibly. + */ + public static void joinUninterruptibly(Thread toJoin, + long timeout, TimeUnit unit) { + Preconditions.checkNotNull(toJoin); + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + while (true) { + try { + // TimeUnit.timedJoin() treats negative timeouts just like zero. + NANOSECONDS.timedJoin(toJoin, remainingNanos); + return; + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code queue.}{@link BlockingQueue#take() take()} uninterruptibly. + */ + public static E takeUninterruptibly(BlockingQueue queue) { + boolean interrupted = false; + try { + while (true) { + try { + return queue.take(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code queue.}{@link BlockingQueue#put(Object) put(element)} + * uninterruptibly. + */ + public static void putUninterruptibly(BlockingQueue queue, E element) { + boolean interrupted = false; + try { + while (true) { + try { + queue.put(element); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + // TODO(user): Support Sleeper somehow (wrapper or interface method)? + /** + * Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)} + * uninterruptibly. + */ + public static void sleepUninterruptibly(long sleepFor, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(sleepFor); + long end = System.nanoTime() + remainingNanos; + while (true) { + try { + // TimeUnit.sleep() treats negative timeouts just like zero. + NANOSECONDS.sleep(remainingNanos); + return; + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + // TODO(user): Add support for waitUninterruptibly. + + private Uninterruptibles() {} +} diff --git a/guava/src/com/google/common/util/concurrent/package-info.java b/guava/src/com/google/common/util/concurrent/package-info.java new file mode 100644 index 0000000..6ea5069 --- /dev/null +++ b/guava/src/com/google/common/util/concurrent/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Concurrency utilities. + * + *

    Commonly used types include {@link + * com.google.common.util.concurrent.ListenableFuture} and {@link + * com.google.common.util.concurrent.Service}. + * + *

    Commonly used utilities include {@link + * com.google.common.util.concurrent.Futures}, {@link + * com.google.common.util.concurrent.MoreExecutors}, and {@link + * com.google.common.util.concurrent.ThreadFactoryBuilder}. + * + *

    This package is a part of the open-source + * Guava libraries. + */ +@ParametersAreNonnullByDefault +package com.google.common.util.concurrent; + +import javax.annotation.ParametersAreNonnullByDefault; + -- cgit v1.1

See the Guava User Guide article on + * {@code Multimap}. + * + * @author Jared Levy + * @since 2.0 (imported from Google Collections Library) + */ +@GwtCompatible +public interface SetMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

See the Guava User Guide article on + * {@code Splitter}. + * + * @author Julien Silland + * @author Jesse Wilson + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Splitter { + private final CharMatcher trimmer; + private final boolean omitEmptyStrings; + private final Strategy strategy; + private final int limit; + + private Splitter(Strategy strategy) { + this(strategy, false, CharMatcher.NONE, Integer.MAX_VALUE); + } + + private Splitter(Strategy strategy, boolean omitEmptyStrings, + CharMatcher trimmer, int limit) { + this.strategy = strategy; + this.omitEmptyStrings = omitEmptyStrings; + this.trimmer = trimmer; + this.limit = limit; + } + + /** + * Returns a splitter that uses the given single-character separator. For + * example, {@code Splitter.on(',').split("foo,,bar")} returns an iterable + * containing {@code ["foo", "", "bar"]}. + * + * @param separator the character to recognize as a separator + * @return a splitter, with default settings, that recognizes that separator + */ + public static Splitter on(char separator) { + return on(CharMatcher.is(separator)); + } + + /** + * Returns a splitter that considers any single character matched by the + * given {@code CharMatcher} to be a separator. For example, {@code + * Splitter.on(CharMatcher.anyOf(";,")).split("foo,;bar,quux")} returns an + * iterable containing {@code ["foo", "", "bar", "quux"]}. + * + * @param separatorMatcher a {@link CharMatcher} that determines whether a + * character is a separator + * @return a splitter, with default settings, that uses this matcher + */ + public static Splitter on(final CharMatcher separatorMatcher) { + checkNotNull(separatorMatcher); + + return new Splitter(new Strategy() { + @Override public SplittingIterator iterator( + Splitter splitter, final CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override int separatorStart(int start) { + return separatorMatcher.indexIn(toSplit, start); + } + + @Override int separatorEnd(int separatorPosition) { + return separatorPosition + 1; + } + }; + } + }); + } + + /** + * Returns a splitter that uses the given fixed string as a separator. For + * example, {@code Splitter.on(", ").split("foo, bar, baz,qux")} returns an + * iterable containing {@code ["foo", "bar", "baz,qux"]}. + * + * @param separator the literal, nonempty string to recognize as a separator + * @return a splitter, with default settings, that recognizes that separator + */ + public static Splitter on(final String separator) { + checkArgument(separator.length() != 0, + "The separator may not be the empty string."); + + return new Splitter(new Strategy() { + @Override public SplittingIterator iterator( + Splitter splitter, CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override public int separatorStart(int start) { + int delimeterLength = separator.length(); + + positions: + for (int p = start, last = toSplit.length() - delimeterLength; + p <= last; p++) { + for (int i = 0; i < delimeterLength; i++) { + if (toSplit.charAt(i + p) != separator.charAt(i)) { + continue positions; + } + } + return p; + } + return -1; + } + + @Override public int separatorEnd(int separatorPosition) { + return separatorPosition + separator.length(); + } + }; + } + }); + } + + /** + * Returns a splitter that considers any subsequence matching {@code + * pattern} to be a separator. For example, {@code + * Splitter.on(Pattern.compile("\r?\n")).split(entireFile)} splits a string + * into lines whether it uses DOS-style or UNIX-style line terminators. + * + * @param separatorPattern the pattern that determines whether a subsequence + * is a separator. This pattern may not match the empty string. + * @return a splitter, with default settings, that uses this pattern + * @throws IllegalArgumentException if {@code separatorPattern} matches the + * empty string + */ + @GwtIncompatible("java.util.regex") + public static Splitter on(final Pattern separatorPattern) { + checkNotNull(separatorPattern); + checkArgument(!separatorPattern.matcher("").matches(), + "The pattern may not match the empty string: %s", separatorPattern); + + return new Splitter(new Strategy() { + @Override public SplittingIterator iterator( + final Splitter splitter, CharSequence toSplit) { + final Matcher matcher = separatorPattern.matcher(toSplit); + return new SplittingIterator(splitter, toSplit) { + @Override public int separatorStart(int start) { + return matcher.find(start) ? matcher.start() : -1; + } + + @Override public int separatorEnd(int separatorPosition) { + return matcher.end(); + } + }; + } + }); + } + + /** + * Returns a splitter that considers any subsequence matching a given + * pattern (regular expression) to be a separator. For example, {@code + * Splitter.onPattern("\r?\n").split(entireFile)} splits a string into lines + * whether it uses DOS-style or UNIX-style line terminators. This is + * equivalent to {@code Splitter.on(Pattern.compile(pattern))}. + * + * @param separatorPattern the pattern that determines whether a subsequence + * is a separator. This pattern may not match the empty string. + * @return a splitter, with default settings, that uses this pattern + * @throws java.util.regex.PatternSyntaxException if {@code separatorPattern} + * is a malformed expression + * @throws IllegalArgumentException if {@code separatorPattern} matches the + * empty string + */ + @GwtIncompatible("java.util.regex") + public static Splitter onPattern(String separatorPattern) { + return on(Pattern.compile(separatorPattern)); + } + + /** + * Returns a splitter that divides strings into pieces of the given length. + * For example, {@code Splitter.fixedLength(2).split("abcde")} returns an + * iterable containing {@code ["ab", "cd", "e"]}. The last piece can be + * smaller than {@code length} but will never be empty. + * + * @param length the desired length of pieces after splitting + * @return a splitter, with default settings, that can split into fixed sized + * pieces + */ + public static Splitter fixedLength(final int length) { + checkArgument(length > 0, "The length may not be less than 1"); + + return new Splitter(new Strategy() { + @Override public SplittingIterator iterator( + final Splitter splitter, CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override public int separatorStart(int start) { + int nextChunkStart = start + length; + return (nextChunkStart < toSplit.length() ? nextChunkStart : -1); + } + + @Override public int separatorEnd(int separatorPosition) { + return separatorPosition; + } + }; + } + }); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but + * automatically omits empty strings from the results. For example, {@code + * Splitter.on(',').omitEmptyStrings().split(",a,,,b,c,,")} returns an + * iterable containing only {@code ["a", "b", "c"]}. + * + *