1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.util.collections;
6
7import org.mockito.internal.util.Checks;
8
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.Set;
14
15import static java.lang.reflect.Array.*;
16
17/**
18 * hashCode and equals safe hash based set.
19 *
20 * <p>
21 *     Useful for holding mocks that have un-stubbable hashCode or equals method,
22 *     meaning that in this scenario the real code is always called and will most probably
23 *     cause an {@link NullPointerException}.
24 * </p>
25 * <p>
26 *     This collection wraps the mock in an augmented type {@link HashCodeAndEqualsMockWrapper}
27 *     that have his own implementation.
28 * </p>
29 *
30 * @see HashCodeAndEqualsMockWrapper
31 */
32public class HashCodeAndEqualsSafeSet implements Set<Object> {
33
34    private HashSet<HashCodeAndEqualsMockWrapper> backingHashSet = new HashSet<HashCodeAndEqualsMockWrapper>();
35
36    public Iterator<Object> iterator() {
37        return new Iterator<Object>() {
38            private Iterator<HashCodeAndEqualsMockWrapper> iterator = backingHashSet.iterator();
39
40            public boolean hasNext() {
41                return iterator.hasNext();
42            }
43
44            public Object next() {
45                return iterator.next().get();
46            }
47
48            public void remove() {
49                iterator.remove();
50            }
51        };
52    }
53
54    public int size() {
55        return backingHashSet.size();
56    }
57
58    public boolean isEmpty() {
59        return backingHashSet.isEmpty();
60    }
61
62    public boolean contains(Object mock) {
63        return backingHashSet.contains(HashCodeAndEqualsMockWrapper.of(mock));
64    }
65
66    public boolean add(Object mock) {
67        return backingHashSet.add(HashCodeAndEqualsMockWrapper.of(mock));
68    }
69
70    public boolean remove(Object mock) {
71        return backingHashSet.remove(HashCodeAndEqualsMockWrapper.of(mock));
72    }
73
74    public void clear() {
75        backingHashSet.clear();
76    }
77
78    @Override public Object clone() throws CloneNotSupportedException {
79        throw new CloneNotSupportedException();
80    }
81
82    @Override public boolean equals(Object o) {
83        if (!(o instanceof HashCodeAndEqualsSafeSet)) {
84            return false;
85        }
86        HashCodeAndEqualsSafeSet that = (HashCodeAndEqualsSafeSet) o;
87        return backingHashSet.equals(that.backingHashSet);
88    }
89
90    @Override public int hashCode() {
91        return backingHashSet.hashCode();
92    }
93
94    public Object[] toArray() {
95        return unwrapTo(new Object[size()]);
96    }
97
98    private <T> T[] unwrapTo(T[] array) {
99        Iterator<Object> iterator = iterator();
100        for (int i = 0, objectsLength = array.length; i < objectsLength; i++) {
101            if (iterator.hasNext()) {
102                array[i] = (T) iterator.next();
103            }
104        }
105        return array;
106    }
107
108
109    public <T> T[] toArray(T[] typedArray) {
110        T[] array = typedArray.length >= size() ? typedArray :
111                (T[]) newInstance(typedArray.getClass().getComponentType(), size());
112        return unwrapTo(array);
113    }
114
115    public boolean removeAll(Collection<?> mocks) {
116        return backingHashSet.removeAll(asWrappedMocks(mocks));
117    }
118
119    public boolean containsAll(Collection<?> mocks) {
120        return backingHashSet.containsAll(asWrappedMocks(mocks));
121    }
122
123    public boolean addAll(Collection<?> mocks) {
124        return backingHashSet.addAll(asWrappedMocks(mocks));
125    }
126
127    public boolean retainAll(Collection<?> mocks) {
128        return backingHashSet.retainAll(asWrappedMocks(mocks));
129    }
130
131    private HashSet<HashCodeAndEqualsMockWrapper> asWrappedMocks(Collection<?> mocks) {
132        Checks.checkNotNull(mocks, "Passed collection should notify() be null");
133        HashSet<HashCodeAndEqualsMockWrapper> hashSet = new HashSet<HashCodeAndEqualsMockWrapper>();
134        for (Object mock : mocks) {
135            assert ! (mock instanceof HashCodeAndEqualsMockWrapper) : "WRONG";
136            hashSet.add(HashCodeAndEqualsMockWrapper.of(mock));
137        }
138        return hashSet;
139    }
140
141    @Override public String toString() {
142        return backingHashSet.toString();
143    }
144
145    public static HashCodeAndEqualsSafeSet of(Object... mocks) {
146        return of(Arrays.asList(mocks));
147    }
148
149    public static HashCodeAndEqualsSafeSet of(Iterable<Object> objects) {
150        HashCodeAndEqualsSafeSet hashCodeAndEqualsSafeSet = new HashCodeAndEqualsSafeSet();
151        if (objects != null) {
152            for (Object mock : objects) {
153                hashCodeAndEqualsSafeSet.add(mock);
154            }
155        }
156        return hashCodeAndEqualsSafeSet;
157    }
158}
159