This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git
The following commit(s) were added to refs/heads/master by this push:
new cfe620965 [COLLECTIONS-896] Add TransformedMultiSet plus minor
functionality ga… (#708)
cfe620965 is described below
commit cfe620965d13f9ecdf6e3c3128041c19be89a3d1
Author: Paul King <[email protected]>
AuthorDate: Fri Jul 10 22:36:28 2026 +1000
[COLLECTIONS-896] Add TransformedMultiSet plus minor functionality ga…
(#708)
* [COLLECTIONS-896] Add TransformedMultiSet plus minor functionality gaps
for MultiSet vs Bags
* Javadoc: Add since 4.6.0
---------
Co-authored-by: Gary Gregory <[email protected]>
---
.../commons/collections4/CollectionUtils.java | 22 +--
.../apache/commons/collections4/IterableUtils.java | 3 +
.../org/apache/commons/collections4/ListUtils.java | 6 +-
.../apache/commons/collections4/MultiMapUtils.java | 15 ++
.../apache/commons/collections4/MultiSetUtils.java | 52 +++++++
.../collections4/multiset/AbstractMapMultiSet.java | 13 ++
.../collections4/multiset/HashMultiSet.java | 10 ++
.../collections4/multiset/TransformedMultiSet.java | 152 +++++++++++++++++++++
.../multiset/TransformedSortedMultiSet.java | 126 +++++++++++++++++
.../collections4/multiset/TreeMultiSet.java | 10 ++
.../collections4/multiset/package-info.java | 1 +
.../commons/collections4/IterableUtilsTest.java | 12 ++
.../commons/collections4/MultiMapUtilsTest.java | 34 +++++
.../commons/collections4/MultiSetUtilsTest.java | 35 +++++
.../collections4/multiset/HashMultiSetTest.java | 11 ++
.../multiset/TransformedMultiSetTest.java | 108 +++++++++++++++
.../multiset/TransformedSortedMultiSetTest.java | 88 ++++++++++++
.../collections4/multiset/TreeMultiSetTest.java | 12 ++
...nsformedMultiSet.emptyCollection.version4.6.obj | Bin 0 -> 494 bytes
...ansformedMultiSet.fullCollection.version4.6.obj | Bin 0 -> 1003 bytes
...edSortedMultiSet.emptyCollection.version4.6.obj | Bin 0 -> 576 bytes
...medSortedMultiSet.fullCollection.version4.6.obj | Bin 0 -> 1123 bytes
22 files changed, 696 insertions(+), 14 deletions(-)
diff --git a/src/main/java/org/apache/commons/collections4/CollectionUtils.java
b/src/main/java/org/apache/commons/collections4/CollectionUtils.java
index 7813dd49e..f57228beb 100644
--- a/src/main/java/org/apache/commons/collections4/CollectionUtils.java
+++ b/src/main/java/org/apache/commons/collections4/CollectionUtils.java
@@ -31,7 +31,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.collection.PredicatedCollection;
import org.apache.commons.collections4.collection.SynchronizedCollection;
import org.apache.commons.collections4.collection.TransformedCollection;
@@ -40,11 +39,12 @@ import
org.apache.commons.collections4.collection.UnmodifiableCollection;
import org.apache.commons.collections4.functors.TruePredicate;
import org.apache.commons.collections4.iterators.CollatingIterator;
import org.apache.commons.collections4.iterators.PermutationIterator;
+import org.apache.commons.collections4.multiset.HashMultiSet;
/**
* Provides utility methods and decorators for {@link Collection} instances.
* <p>
- * Various utility methods might put the input objects into a Set/Map/Bag. In
case
+ * Various utility methods might put the input objects into a
Set/Map/MultiSet. In case
* the input objects override {@link Object#equals(Object)}, it is mandatory
that
* the general contract of the {@link Object#hashCode()} method is maintained.
* </p>
@@ -64,14 +64,14 @@ public class CollectionUtils {
private static class CardinalityHelper<O> {
static boolean equals(final Collection<?> a, final Collection<?> b) {
- return new HashBag<>(a).equals(new HashBag<>(b));
+ return new HashMultiSet<>(a).equals(new HashMultiSet<>(b));
}
/** Contains the cardinality for each object in collection A. */
- final Bag<O> cardinalityA;
+ final MultiSet<O> cardinalityA;
/** Contains the cardinality for each object in collection B. */
- final Bag<O> cardinalityB;
+ final MultiSet<O> cardinalityB;
/**
* Creates a new CardinalityHelper for two collections.
@@ -80,8 +80,8 @@ public class CollectionUtils {
* @param b the second collection
*/
CardinalityHelper(final Iterable<? extends O> a, final Iterable<?
extends O> b) {
- cardinalityA = new HashBag<>(a);
- cardinalityB = new HashBag<>(b);
+ cardinalityA = new HashMultiSet<>(a);
+ cardinalityB = new HashMultiSet<>(b);
}
/**
@@ -104,7 +104,7 @@ public class CollectionUtils {
return getFreq(key, cardinalityB);
}
- private int getFreq(final Object key, final Bag<?> freqMap) {
+ private int getFreq(final Object key, final MultiSet<?> freqMap) {
return freqMap.getCount(key);
}
@@ -1989,14 +1989,14 @@ public class CollectionUtils {
Objects.requireNonNull(b, "b");
Objects.requireNonNull(p, "p");
final ArrayList<O> list = new ArrayList<>();
- final HashBag<O> bag = new HashBag<>();
+ final HashMultiSet<O> multiSet = new HashMultiSet<>();
for (final O element : b) {
if (p.test(element)) {
- bag.add(element);
+ multiSet.add(element);
}
}
for (final O element : a) {
- if (!bag.remove(element, 1)) {
+ if (multiSet.remove(element, 1) == 0) {
list.add(element);
}
}
diff --git a/src/main/java/org/apache/commons/collections4/IterableUtils.java
b/src/main/java/org/apache/commons/collections4/IterableUtils.java
index b96d35e48..b7eed16d4 100644
--- a/src/main/java/org/apache/commons/collections4/IterableUtils.java
+++ b/src/main/java/org/apache/commons/collections4/IterableUtils.java
@@ -540,6 +540,9 @@ public class IterableUtils {
if (iterable instanceof Set<?>) {
return ((Set<E>) iterable).contains(obj) ? 1 : 0;
}
+ if (iterable instanceof MultiSet<?>) {
+ return ((MultiSet<E>) iterable).getCount(obj);
+ }
if (iterable instanceof Bag<?>) {
return ((Bag<E>) iterable).getCount(obj);
}
diff --git a/src/main/java/org/apache/commons/collections4/ListUtils.java
b/src/main/java/org/apache/commons/collections4/ListUtils.java
index 5a35e28b2..308b5920b 100644
--- a/src/main/java/org/apache/commons/collections4/ListUtils.java
+++ b/src/main/java/org/apache/commons/collections4/ListUtils.java
@@ -25,13 +25,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.functors.DefaultEquator;
import org.apache.commons.collections4.list.FixedSizeList;
import org.apache.commons.collections4.list.LazyList;
import org.apache.commons.collections4.list.PredicatedList;
import org.apache.commons.collections4.list.TransformedList;
import org.apache.commons.collections4.list.UnmodifiableList;
+import org.apache.commons.collections4.multiset.HashMultiSet;
import org.apache.commons.collections4.sequence.CommandVisitor;
import org.apache.commons.collections4.sequence.EditScript;
import org.apache.commons.collections4.sequence.SequencesComparator;
@@ -654,9 +654,9 @@ public class ListUtils {
*/
public static <E> List<E> subtract(final List<E> list1, final List<?
extends E> list2) {
final ArrayList<E> result = new ArrayList<>();
- final HashBag<E> bag = new HashBag<>(list2);
+ final HashMultiSet<E> multiSet = new HashMultiSet<>(list2);
for (final E e : list1) {
- if (!bag.remove(e, 1)) {
+ if (multiSet.remove(e, 1) == 0) {
result.add(e);
}
}
diff --git a/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
b/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
index 4802247d8..a1021dbe2 100644
--- a/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
+++ b/src/main/java/org/apache/commons/collections4/MultiMapUtils.java
@@ -28,6 +28,7 @@ import
org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.collections4.multimap.TransformedMultiValuedMap;
import org.apache.commons.collections4.multimap.UnmodifiableMultiValuedMap;
+import org.apache.commons.collections4.multiset.HashMultiSet;
/**
* Provides utility methods and decorators for {@link MultiValuedMap}
instances.
@@ -114,6 +115,20 @@ public class MultiMapUtils {
return map != null ? new ArrayList<>(map.get(key)) : null;
}
+ /**
+ * Gets a MultiSet from {@code MultiValuedMap} in a null-safe manner.
+ *
+ * @param <K> The key type.
+ * @param <V> The value type.
+ * @param map the {@link MultiValuedMap} to use.
+ * @param key the key to look up.
+ * @return a new MultiSet containing the values from the {@link
MultiValuedMap}, or null if input map is null.
+ * @since 4.6.0
+ */
+ public static <K, V> MultiSet<V> getValuesAsMultiSet(final
MultiValuedMap<K, V> map, final K key) {
+ return map != null ? new HashMultiSet<>(map.get(key)) : null;
+ }
+
/**
* Gets a Set from {@code MultiValuedMap} in a null-safe manner.
*
diff --git a/src/main/java/org/apache/commons/collections4/MultiSetUtils.java
b/src/main/java/org/apache/commons/collections4/MultiSetUtils.java
index 7445bc866..d6e2d009b 100644
--- a/src/main/java/org/apache/commons/collections4/MultiSetUtils.java
+++ b/src/main/java/org/apache/commons/collections4/MultiSetUtils.java
@@ -24,6 +24,8 @@ import
org.apache.commons.collections4.multiset.PredicatedMultiSet;
import org.apache.commons.collections4.multiset.PredicatedSortedMultiSet;
import org.apache.commons.collections4.multiset.SynchronizedMultiSet;
import org.apache.commons.collections4.multiset.SynchronizedSortedMultiSet;
+import org.apache.commons.collections4.multiset.TransformedMultiSet;
+import org.apache.commons.collections4.multiset.TransformedSortedMultiSet;
import org.apache.commons.collections4.multiset.TreeMultiSet;
import org.apache.commons.collections4.multiset.UnmodifiableMultiSet;
import org.apache.commons.collections4.multiset.UnmodifiableSortedMultiSet;
@@ -281,6 +283,56 @@ public class MultiSetUtils {
return SynchronizedSortedMultiSet.synchronizedSortedMultiSet(multiset);
}
+ /**
+ * Returns a transformed multiset backed by the given multiset.
+ * <p>
+ * Each object is passed through the transformer as it is added to the
+ * MultiSet. It is important not to use the original multiset after
invoking this
+ * method, as it is a backdoor for adding untransformed objects.
+ * </p>
+ * <p>
+ * Existing entries in the specified multiset will not be transformed.
+ * If you want that behavior, see
+ * {@link TransformedMultiSet#transformedMultiSet(MultiSet, Transformer)}.
+ * </p>
+ *
+ * @param <E> The element type
+ * @param multiset the multiset to transform, must not be null
+ * @param transformer the transformer for the multiset, must not be null
+ * @return a transformed multiset backed by the given multiset
+ * @throws NullPointerException if the MultiSet or Transformer is null
+ * @since 4.6.0
+ */
+ public static <E> MultiSet<E> transformingMultiSet(final MultiSet<E>
multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ return TransformedMultiSet.transformingMultiSet(multiset, transformer);
+ }
+
+ /**
+ * Returns a transformed sorted multiset backed by the given multiset.
+ * <p>
+ * Each object is passed through the transformer as it is added to the
+ * MultiSet. It is important not to use the original multiset after
invoking this
+ * method, as it is a backdoor for adding untransformed objects.
+ * </p>
+ * <p>
+ * Existing entries in the specified multiset will not be transformed.
+ * If you want that behavior, see
+ * {@link
TransformedSortedMultiSet#transformedSortedMultiSet(SortedMultiSet,
Transformer)}.
+ * </p>
+ *
+ * @param <E> The element type
+ * @param multiset the sorted multiset to transform, must not be null
+ * @param transformer the transformer for the multiset, must not be null
+ * @return a transformed sorted multiset backed by the given multiset
+ * @throws NullPointerException if the SortedMultiSet or Transformer is
null
+ * @since 4.6.0
+ */
+ public static <E> SortedMultiSet<E> transformingSortedMultiSet(final
SortedMultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ return TransformedSortedMultiSet.transformingSortedMultiSet(multiset,
transformer);
+ }
+
/**
* Returns an unmodifiable view of the given multiset. Any modification
attempts
* to the returned multiset will raise an {@link
UnsupportedOperationException}.
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java
index fc1dc9907..4d62bc47a 100644
---
a/src/main/java/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java
+++
b/src/main/java/org/apache/commons/collections4/multiset/AbstractMapMultiSet.java
@@ -302,6 +302,19 @@ public abstract class AbstractMapMultiSet<E> extends
AbstractMultiSet<E> {
this.map = map;
}
+ /**
+ * Constructs a new instance that assigns the specified Map as the backing
store. The map
+ * must be empty and non-null. The multiset is filled from the iterable
elements.
+ *
+ * @param map the map to assign.
+ * @param iterable the iterable of elements to add.
+ * @since 4.6.0
+ */
+ protected AbstractMapMultiSet(final Map<E, MutableInteger> map, final
Iterable<? extends E> iterable) {
+ this(map);
+ iterable.forEach(this::add);
+ }
+
@Override
public int add(final E object, final int occurrences) {
if (occurrences < 0) {
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/HashMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/HashMultiSet.java
index c31cbec9e..53e78f2de 100644
--- a/src/main/java/org/apache/commons/collections4/multiset/HashMultiSet.java
+++ b/src/main/java/org/apache/commons/collections4/multiset/HashMultiSet.java
@@ -67,6 +67,16 @@ public class HashMultiSet<E> extends AbstractMapMultiSet<E>
implements Serializa
addAll(coll);
}
+ /**
+ * Constructs a multiset containing all the members of the given Iterable.
+ *
+ * @param iterable an iterable to copy into this multiset.
+ * @since 4.6.0
+ */
+ public HashMultiSet(final Iterable<? extends E> iterable) {
+ super(new HashMap<>(), iterable);
+ }
+
/**
* Deserializes the multiset in using a custom routine.
*
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/TransformedMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/TransformedMultiSet.java
new file mode 100644
index 000000000..66565892e
--- /dev/null
+++
b/src/main/java/org/apache/commons/collections4/multiset/TransformedMultiSet.java
@@ -0,0 +1,152 @@
+/*
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.multiset;
+
+import java.util.Set;
+
+import org.apache.commons.collections4.MultiSet;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.collection.TransformedCollection;
+import org.apache.commons.collections4.set.TransformedSet;
+
+/**
+ * Decorates another {@link MultiSet} to transform objects that are added.
+ * <p>
+ * The add and setCount methods are affected by this class.
+ * Thus objects must be removed or searched for using their transformed form.
+ * For example, if the transformation converts Strings to Integers, you must
+ * use the Integer form to remove objects.
+ * </p>
+ *
+ * @param <E> The type held in the multiset
+ * @since 4.6.0
+ */
+public class TransformedMultiSet<E> extends TransformedCollection<E>
implements MultiSet<E> {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 20260710L;
+
+ /**
+ * Factory method to create a transforming multiset that will transform
+ * existing contents of the specified multiset.
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * will be transformed by this method.
+ * Contrast this with {@link #transformingMultiSet(MultiSet, Transformer)}.
+ *
+ * @param <E> The type of the elements in the multiset
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @return a new transformed MultiSet
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ public static <E> TransformedMultiSet<E> transformedMultiSet(final
MultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ final TransformedMultiSet<E> decorated = new
TransformedMultiSet<>(multiset, transformer);
+ if (!multiset.isEmpty()) {
+ @SuppressWarnings("unchecked") // multiset is of type E
+ final E[] values = (E[]) multiset.toArray(); // NOPMD - false
positive for generics
+ multiset.clear();
+ for (final E value : values) {
+ decorated.decorated().add(transformer.apply(value));
+ }
+ }
+ return decorated;
+ }
+
+ /**
+ * Factory method to create a transforming multiset.
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * are NOT transformed. Contrast this with {@link
#transformedMultiSet(MultiSet, Transformer)}.
+ *
+ * @param <E> The type of the elements in the multiset
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @return a new transformed MultiSet
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ public static <E> TransformedMultiSet<E> transformingMultiSet(final
MultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ return new TransformedMultiSet<>(multiset, transformer);
+ }
+
+ /**
+ * Constructor that wraps (not copies).
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * are NOT transformed.
+ *
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ protected TransformedMultiSet(final MultiSet<E> multiset, final
Transformer<? super E, ? extends E> transformer) {
+ super(multiset, transformer);
+ }
+
+ @Override
+ public int add(final E object, final int occurrences) {
+ return getMultiSet().add(transform(object), occurrences);
+ }
+
+ @Override
+ public Set<MultiSet.Entry<E>> entrySet() {
+ return getMultiSet().entrySet();
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ return object == this || decorated().equals(object);
+ }
+
+ @Override
+ public int getCount(final Object object) {
+ return getMultiSet().getCount(object);
+ }
+
+ /**
+ * Gets the decorated multiset.
+ *
+ * @return the decorated multiset
+ */
+ protected MultiSet<E> getMultiSet() {
+ return (MultiSet<E>) decorated();
+ }
+
+ @Override
+ public int hashCode() {
+ return decorated().hashCode();
+ }
+
+ @Override
+ public int remove(final Object object, final int occurrences) {
+ return getMultiSet().remove(object, occurrences);
+ }
+
+ @Override
+ public int setCount(final E object, final int count) {
+ return getMultiSet().setCount(transform(object), count);
+ }
+
+ @Override
+ public Set<E> uniqueSet() {
+ final Set<E> set = getMultiSet().uniqueSet();
+ return TransformedSet.<E>transformingSet(set, transformer);
+ }
+
+}
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSet.java
new file mode 100644
index 000000000..77145656f
--- /dev/null
+++
b/src/main/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSet.java
@@ -0,0 +1,126 @@
+/*
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.multiset;
+
+import java.util.Comparator;
+
+import org.apache.commons.collections4.SortedMultiSet;
+import org.apache.commons.collections4.Transformer;
+
+/**
+ * Decorates another {@link SortedMultiSet} to transform objects that are
added.
+ * <p>
+ * The add and setCount methods are affected by this class.
+ * Thus objects must be removed or searched for using their transformed form.
+ * For example, if the transformation converts Strings to Integers, you must
+ * use the Integer form to remove objects.
+ * </p>
+ *
+ * @param <E> The type held in the multiset
+ * @since 4.6.0
+ */
+public class TransformedSortedMultiSet<E> extends TransformedMultiSet<E>
implements SortedMultiSet<E> {
+
+ /** Serialization version */
+ private static final long serialVersionUID = 20260710L;
+
+ /**
+ * Factory method to create a transforming sorted multiset that will
transform
+ * existing contents of the specified sorted multiset.
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * will be transformed by this method.
+ * Contrast this with {@link #transformingSortedMultiSet(SortedMultiSet,
Transformer)}.
+ *
+ * @param <E> The type of the elements in the multiset
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @return a new transformed SortedMultiSet
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ public static <E> TransformedSortedMultiSet<E>
transformedSortedMultiSet(final SortedMultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ final TransformedSortedMultiSet<E> decorated = new
TransformedSortedMultiSet<>(multiset, transformer);
+ if (!multiset.isEmpty()) {
+ @SuppressWarnings("unchecked") // multiset is of type E
+ final E[] values = (E[]) multiset.toArray(); // NOPMD - false
positive for generics
+ multiset.clear();
+ for (final E value : values) {
+ decorated.decorated().add(transformer.apply(value));
+ }
+ }
+ return decorated;
+ }
+
+ /**
+ * Factory method to create a transforming sorted multiset.
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * are NOT transformed. Contrast this with
+ * {@link #transformedSortedMultiSet(SortedMultiSet, Transformer)}.
+ *
+ * @param <E> The type of the elements in the multiset
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @return a new transformed SortedMultiSet
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ public static <E> TransformedSortedMultiSet<E>
transformingSortedMultiSet(final SortedMultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ return new TransformedSortedMultiSet<>(multiset, transformer);
+ }
+
+ /**
+ * Constructor that wraps (not copies).
+ * <p>
+ * If there are any elements already in the multiset being decorated, they
+ * are NOT transformed.
+ *
+ * @param multiset the multiset to decorate, must not be null
+ * @param transformer the transformer to use for conversion, must not be
null
+ * @throws NullPointerException if multiset or transformer is null
+ */
+ protected TransformedSortedMultiSet(final SortedMultiSet<E> multiset,
+ final Transformer<? super E, ? extends E> transformer) {
+ super(multiset, transformer);
+ }
+
+ @Override
+ public Comparator<? super E> comparator() {
+ return getSortedMultiSet().comparator();
+ }
+
+ @Override
+ public E first() {
+ return getSortedMultiSet().first();
+ }
+
+ /**
+ * Gets the decorated sorted multiset.
+ *
+ * @return the decorated sorted multiset
+ */
+ protected SortedMultiSet<E> getSortedMultiSet() {
+ return (SortedMultiSet<E>) decorated();
+ }
+
+ @Override
+ public E last() {
+ return getSortedMultiSet().last();
+ }
+
+}
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/TreeMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/TreeMultiSet.java
index 29153b9f5..1d7909d17 100644
--- a/src/main/java/org/apache/commons/collections4/multiset/TreeMultiSet.java
+++ b/src/main/java/org/apache/commons/collections4/multiset/TreeMultiSet.java
@@ -86,6 +86,16 @@ public class TreeMultiSet<E> extends AbstractMapMultiSet<E>
implements SortedMul
super(new TreeMap<>(comparator));
}
+ /**
+ * Constructs a multiset containing all the members of the given Iterable.
+ *
+ * @param iterable an iterable to copy into this multiset.
+ * @since 4.6.0
+ */
+ public TreeMultiSet(final Iterable<? extends E> iterable) {
+ super(new TreeMap<>(), iterable);
+ }
+
/**
* {@inheritDoc}
*
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/package-info.java
b/src/main/java/org/apache/commons/collections4/multiset/package-info.java
index 7a34296e1..a4ab1728d 100644
--- a/src/main/java/org/apache/commons/collections4/multiset/package-info.java
+++ b/src/main/java/org/apache/commons/collections4/multiset/package-info.java
@@ -32,6 +32,7 @@
* <ul>
* <li>Predicated - ensures that only elements that are valid according to
a predicate can be added</li>
* <li>Synchronized - synchronizes method access for multithreaded
environments</li>
+ * <li>Transformed - transforms elements as they are added</li>
* <li>Unmodifiable - ensures the multiset cannot be altered</li>
* </ul>
*/
diff --git
a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java
b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java
index 79e014fc5..80622cb32 100644
--- a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java
@@ -39,6 +39,7 @@ import java.util.Set;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.functors.EqualPredicate;
+import org.apache.commons.collections4.multiset.HashMultiSet;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -423,6 +424,17 @@ class IterableUtilsTest {
assertEquals(1, IterableUtils.frequency(bag, "C"));
assertEquals(0, IterableUtils.frequency(bag, "D"));
assertEquals(2, IterableUtils.frequency(bag, "E"));
+
+ final MultiSet<String> multiSet = new HashMultiSet<>();
+ multiSet.add("A", 3);
+ multiSet.add("C");
+ multiSet.add("E");
+ multiSet.add("E");
+ assertEquals(3, IterableUtils.frequency(multiSet, "A"));
+ assertEquals(0, IterableUtils.frequency(multiSet, "B"));
+ assertEquals(1, IterableUtils.frequency(multiSet, "C"));
+ assertEquals(0, IterableUtils.frequency(multiSet, "D"));
+ assertEquals(2, IterableUtils.frequency(multiSet, "E"));
}
@Test
diff --git
a/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
b/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
index c399ece0d..4d0375e95 100644
--- a/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/MultiMapUtilsTest.java
@@ -37,6 +37,7 @@ import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import
org.apache.commons.collections4.multimap.LinkedHashSetValuedLinkedHashMap;
+import org.apache.commons.collections4.multiset.HashMultiSet;
import org.junit.jupiter.api.Test;
/**
@@ -132,6 +133,39 @@ class MultiMapUtilsTest {
assertFalse(map.containsMapping("key1", "v4"));
}
+ @Test
+ void testGetValuesAsMultiSet() {
+ assertNull(MultiMapUtils.getValuesAsMultiSet(null, "key1"));
+ final String[] values = { "v1", "v2", "v3" };
+ final MultiValuedMap<String, String> map = new
ArrayListValuedHashMap<>();
+ for (final String val : values) {
+ map.put("key1", val);
+ map.put("key1", val);
+ }
+ final MultiSet<String> multiSet =
MultiMapUtils.getValuesAsMultiSet(map, "key1");
+ assertEquals(6, multiSet.size());
+ for (final String val : values) {
+ assertTrue(multiSet.contains(val));
+ assertEquals(2, multiSet.getCount(val));
+ }
+ assertTrue(MultiMapUtils.getValuesAsMultiSet(map, null).isEmpty());
+ assertTrue(MultiMapUtils.getValuesAsMultiSet(map,
"MISSING_KEY").isEmpty());
+ }
+
+ @Test
+ void testGetValuesAsMultiSetIsSafeCopy() {
+ final String[] values = { "v1", "v2", "v3" };
+ final MultiValuedMap<String, String> mockMap =
createMock(MultiValuedMap.class);
+ final MultiSet<String> multiSetToReturn = new HashMultiSet<>();
+ multiSetToReturn.addAll(Arrays.asList(values));
+ expect(mockMap.get("key1")).andReturn(multiSetToReturn);
+ replay(mockMap);
+ final MultiSet<String> multiSet =
MultiMapUtils.getValuesAsMultiSet(mockMap, "key1");
+ multiSet.add("v4");
+ assertFalse(multiSetToReturn.contains("v4"));
+ verify(mockMap);
+ }
+
@Test
void testGetValuesAsSet() {
assertNull(MultiMapUtils.getValuesAsSet(null, "key1"));
diff --git
a/src/test/java/org/apache/commons/collections4/MultiSetUtilsTest.java
b/src/test/java/org/apache/commons/collections4/MultiSetUtilsTest.java
index 236cfd278..09bab0968 100644
--- a/src/test/java/org/apache/commons/collections4/MultiSetUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/MultiSetUtilsTest.java
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import org.apache.commons.collections4.multiset.HashMultiSet;
+import org.apache.commons.collections4.multiset.TreeMultiSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -160,6 +161,40 @@ class MultiSetUtilsTest {
synced.add("a"); // ensure adding works
}
+ /**
+ * Tests {@link MultiSetUtils#transformingMultiSet(MultiSet,
org.apache.commons.collections4.Transformer)}.
+ */
+ @Test
+ void testTransformingMultiSet() {
+ final MultiSet<String> transformed =
MultiSetUtils.transformingMultiSet(new HashMultiSet<>(), String::toUpperCase);
+ transformed.add("a");
+ assertEquals(1, transformed.getCount("A"));
+ assertEquals(0, transformed.getCount("a"));
+
+ assertThrows(NullPointerException.class, () ->
MultiSetUtils.<String>transformingMultiSet(null, String::toUpperCase),
+ "Expecting NPE");
+ assertThrows(NullPointerException.class, () ->
MultiSetUtils.transformingMultiSet(multiSet, null),
+ "Expecting NPE");
+ }
+
+ /**
+ * Tests {@link MultiSetUtils#transformingSortedMultiSet(SortedMultiSet,
org.apache.commons.collections4.Transformer)}.
+ */
+ @Test
+ void testTransformingSortedMultiSet() {
+ final SortedMultiSet<String> transformed =
MultiSetUtils.transformingSortedMultiSet(new TreeMultiSet<>(),
String::toUpperCase);
+ transformed.add("b");
+ transformed.add("a");
+ assertEquals(2, transformed.size());
+ assertEquals("A", transformed.first());
+ assertEquals("B", transformed.last());
+
+ assertThrows(NullPointerException.class, () ->
MultiSetUtils.<String>transformingSortedMultiSet(null, String::toUpperCase),
+ "Expecting NPE");
+ assertThrows(NullPointerException.class, () ->
MultiSetUtils.transformingSortedMultiSet(new TreeMultiSet<>(), null),
+ "Expecting NPE");
+ }
+
/**
* Tests {@link
MultiSetUtils#unmodifiableMultiSet(org.apache.commons.collections4.MultiSet)
()}.
*/
diff --git
a/src/test/java/org/apache/commons/collections4/multiset/HashMultiSetTest.java
b/src/test/java/org/apache/commons/collections4/multiset/HashMultiSetTest.java
index f8e47428d..139d95c04 100644
---
a/src/test/java/org/apache/commons/collections4/multiset/HashMultiSetTest.java
+++
b/src/test/java/org/apache/commons/collections4/multiset/HashMultiSetTest.java
@@ -16,9 +16,11 @@
*/
package org.apache.commons.collections4.multiset;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.InvalidObjectException;
+import java.util.Arrays;
import org.apache.commons.collections4.MultiSet;
import org.junit.jupiter.api.Test;
@@ -44,6 +46,15 @@ public class HashMultiSetTest<T> extends
AbstractMultiSetTest<T> {
return new HashMultiSet<>();
}
+ @Test
+ void testConstructorFromIterable() {
+ final Iterable<String> iterable = () -> Arrays.asList("a", "b",
"a").iterator();
+ final MultiSet<String> multiset = new HashMultiSet<>(iterable);
+ assertEquals(3, multiset.size());
+ assertEquals(2, multiset.getCount("a"));
+ assertEquals(1, multiset.getCount("b"));
+ }
+
@Test
void testDeserializeRejectsNonPositiveCount() throws Exception {
final int marker = 0x11223344;
diff --git
a/src/test/java/org/apache/commons/collections4/multiset/TransformedMultiSetTest.java
b/src/test/java/org/apache/commons/collections4/multiset/TransformedMultiSetTest.java
new file mode 100644
index 000000000..855b5c726
--- /dev/null
+++
b/src/test/java/org/apache/commons/collections4/multiset/TransformedMultiSetTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.multiset;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.collections4.MultiSet;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.collection.TransformedCollectionTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Extension of {@link AbstractMultiSetTest} for exercising the
+ * {@link TransformedMultiSet} implementation.
+ */
+public class TransformedMultiSetTest<T> extends AbstractMultiSetTest<T> {
+
+ @Override
+ public String getCompatibilityVersion() {
+ return "4.6";
+ }
+
+ @Override
+ protected int getIterationBehaviour() {
+ return UNORDERED;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public MultiSet<T> makeObject() {
+ return TransformedMultiSet.transformingMultiSet(new HashMultiSet<>(),
+ (Transformer<T, T>)
TransformedCollectionTest.NOOP_TRANSFORMER);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testSetCount() {
+ //T had better be Object!
+ final MultiSet<T> multiset =
TransformedMultiSet.transformingMultiSet(new HashMultiSet<>(),
+ (Transformer<T, T>)
TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
+ multiset.setCount((T) "3", 4);
+ assertEquals(4, multiset.getCount(Integer.valueOf(3)));
+ assertEquals(0, multiset.getCount("3"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testTransformedMultiSet() {
+ //T had better be Object!
+ final MultiSet<T> multiset =
TransformedMultiSet.transformingMultiSet(new HashMultiSet<>(),
+ (Transformer<T, T>)
TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
+ assertTrue(multiset.isEmpty());
+ final Object[] els = {"1", "3", "5", "7", "2", "4", "6"};
+ for (int i = 0; i < els.length; i++) {
+ multiset.add((T) els[i]);
+ assertEquals(i + 1, multiset.size());
+ assertTrue(multiset.contains(Integer.valueOf((String) els[i])));
+ assertFalse(multiset.contains(els[i]));
+ }
+
+ assertFalse(multiset.remove(els[0]));
+ assertTrue(multiset.remove(Integer.valueOf((String) els[0])));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testTransformedMultiSet_decorateTransform() {
+ final MultiSet<T> originalMultiSet = new HashMultiSet<>();
+ final Object[] els = {"1", "3", "5", "7", "2", "4", "6"};
+ for (final Object el : els) {
+ originalMultiSet.add((T) el);
+ }
+ final MultiSet<T> multiset =
TransformedMultiSet.transformedMultiSet(originalMultiSet,
+ (Transformer<T, T>)
TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
+ assertEquals(els.length, multiset.size());
+ for (final Object el : els) {
+ assertTrue(multiset.contains(Integer.valueOf((String) el)));
+ assertFalse(multiset.contains(el));
+ }
+
+ assertFalse(multiset.remove(els[0]));
+ assertTrue(multiset.remove(Integer.valueOf((String) els[0])));
+ }
+
+// void testCreate() throws Exception {
+// MultiSet<T> multiset = makeObject();
+// writeExternalFormToDisk((java.io.Serializable) multiset,
"src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.emptyCollection.version4.6.obj");
+// multiset = makeFullCollection();
+// writeExternalFormToDisk((java.io.Serializable) multiset,
"src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.fullCollection.version4.6.obj");
+// }
+
+}
diff --git
a/src/test/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSetTest.java
b/src/test/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSetTest.java
new file mode 100644
index 000000000..e6f83b1c1
--- /dev/null
+++
b/src/test/java/org/apache/commons/collections4/multiset/TransformedSortedMultiSetTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.multiset;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.collections4.SortedMultiSet;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.collection.TransformedCollectionTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Extension of {@link AbstractSortedMultiSetTest} for exercising the
+ * {@link TransformedSortedMultiSet} implementation.
+ */
+public class TransformedSortedMultiSetTest<T> extends
AbstractSortedMultiSetTest<T> {
+
+ @Override
+ public String getCompatibilityVersion() {
+ return "4.6";
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public SortedMultiSet<T> makeObject() {
+ return TransformedSortedMultiSet.transformingSortedMultiSet(new
TreeMultiSet<>(),
+ (Transformer<T, T>)
TransformedCollectionTest.NOOP_TRANSFORMER);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testTransformedMultiSet() {
+ final SortedMultiSet<T> multiset =
TransformedSortedMultiSet.transformingSortedMultiSet(new TreeMultiSet<>(),
+ (Transformer<T, T>)
TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
+ assertEquals(0, multiset.size());
+ final Object[] els = {"1", "3", "5", "7", "2", "4", "6"};
+ for (int i = 0; i < els.length; i++) {
+ multiset.add((T) els[i]);
+ assertEquals(i + 1, multiset.size());
+ assertTrue(multiset.contains(Integer.valueOf((String) els[i])));
+ }
+ assertEquals(Integer.valueOf(1), multiset.first());
+ assertEquals(Integer.valueOf(7), multiset.last());
+
+ assertTrue(multiset.remove(Integer.valueOf((String) els[0])));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void testTransformedMultiSet_decorateTransform() {
+ final TreeMultiSet<T> originalMultiSet = new TreeMultiSet<>();
+ final Object[] els = {"1", "3", "5", "7", "2", "4", "6"};
+ for (final Object el : els) {
+ originalMultiSet.add((T) el);
+ }
+ final SortedMultiSet<T> multiset =
TransformedSortedMultiSet.transformedSortedMultiSet(originalMultiSet,
+ (Transformer<T, T>)
TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
+ assertEquals(els.length, multiset.size());
+ for (final Object el : els) {
+ assertTrue(multiset.contains(Integer.valueOf((String) el)));
+ }
+
+ assertTrue(multiset.remove(Integer.valueOf((String) els[0])));
+ }
+
+// void testCreate() throws Exception {
+// SortedMultiSet<T> multiset = makeObject();
+// writeExternalFormToDisk((java.io.Serializable) multiset,
"src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.emptyCollection.version4.6.obj");
+// multiset = makeFullCollection();
+// writeExternalFormToDisk((java.io.Serializable) multiset,
"src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.fullCollection.version4.6.obj");
+// }
+
+}
diff --git
a/src/test/java/org/apache/commons/collections4/multiset/TreeMultiSetTest.java
b/src/test/java/org/apache/commons/collections4/multiset/TreeMultiSetTest.java
index 3b040164f..79fcaf834 100644
---
a/src/test/java/org/apache/commons/collections4/multiset/TreeMultiSetTest.java
+++
b/src/test/java/org/apache/commons/collections4/multiset/TreeMultiSetTest.java
@@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.util.Arrays;
+
import org.apache.commons.collections4.MultiSet;
import org.apache.commons.collections4.SortedMultiSet;
import org.junit.jupiter.api.Test;
@@ -50,6 +52,16 @@ public class TreeMultiSetTest<T> extends
AbstractSortedMultiSetTest<T> {
return multiset;
}
+ @Test
+ void testConstructorFromIterable() {
+ final Iterable<String> iterable = () -> Arrays.asList("b", "a",
"b").iterator();
+ final SortedMultiSet<String> multiset = new TreeMultiSet<>(iterable);
+ assertEquals(3, multiset.size());
+ assertEquals(2, multiset.getCount("b"));
+ assertEquals("a", multiset.first());
+ assertEquals("b", multiset.last());
+ }
+
@Test
void testAddNonComparable() {
final MultiSet<Object> multiset = new TreeMultiSet<>();
diff --git
a/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.emptyCollection.version4.6.obj
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.emptyCollection.version4.6.obj
new file mode 100644
index 000000000..19bc5f715
Binary files /dev/null and
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.emptyCollection.version4.6.obj
differ
diff --git
a/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.fullCollection.version4.6.obj
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.fullCollection.version4.6.obj
new file mode 100644
index 000000000..2ab1f40c9
Binary files /dev/null and
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedMultiSet.fullCollection.version4.6.obj
differ
diff --git
a/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.emptyCollection.version4.6.obj
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.emptyCollection.version4.6.obj
new file mode 100644
index 000000000..db78edf22
Binary files /dev/null and
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.emptyCollection.version4.6.obj
differ
diff --git
a/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.fullCollection.version4.6.obj
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.fullCollection.version4.6.obj
new file mode 100644
index 000000000..3596fbbfa
Binary files /dev/null and
b/src/test/resources/org/apache/commons/collections4/data/test/TransformedSortedMultiSet.fullCollection.version4.6.obj
differ