{
23 |
24 | /**
25 | * Returns the element by its index. Supports negative indices in "python-way".
26 | *
27 | * {@code
28 | * ImmutableList list = getList(); // [1, 2, 3, 4, 5]
29 | * list.get(1); // 2
30 | * list.get(3); // 4
31 | * list.get(-1); // 5
32 | * list.get(-3); // 3
33 | * list.get(-5); // 1;
34 | * list.get(0); // 1;
35 | * }
36 | *
37 | * If {@code index} is less than zero, then {@code list.get(index)} equals to {@code
38 | * list.get(list.size() - Math.abs(index))}
39 | *
40 | * @param index index of the element to return
41 | * @return the element at the specified position
42 | * @throws IndexOutOfBoundsException if the index is bigger than or equal to {@code list.size()}
43 | * or {@code list.size() - Math.abs(index)} is less than zero if
44 | * index is negative
45 | */
46 | T get(int index);
47 |
48 | /**
49 | * Returns the first index of the specified element or {@link OptionalInt#empty()}, if element is
50 | * not found.
51 | *
52 | * @param element the searching element
53 | * @return the first index of the {@link OptionalInt#empty()}
54 | */
55 | OptionalInt indexOf(T element);
56 |
57 | /**
58 | * Returns the last index of the specified element or {@link OptionalInt#empty()}, if element is
59 | * not found.
60 | *
61 | * @param element the searching element
62 | * @return the first index of the element or {@link OptionalInt#empty()}
63 | */
64 | OptionalInt lastIndexOf(T element);
65 |
66 | /**
67 | * Returns sublist starting from {@code fromIndex}. Supports negative indices.
68 | *
69 | * {@code
70 | * ImmutableList list = getList(); // [1, 2, 3, 4, 5, 6]
71 | * list.slice(1); // [2, 3, 4, 5, 6]
72 | * list.slice(-2); // [5, 6]
73 | * list.slice(-4); // [3, 4, 5, 6]
74 | * }
75 | *
76 | * @param fromIndex start index (inclusively)
77 | * @return sublist
78 | * @throws IndexOutOfBoundsException if {@code fromIndex} is out of bounds
79 | * @see ImmutableList#slice(int, int, int)
80 | */
81 | ImmutableList slice(int fromIndex);
82 |
83 | /**
84 | * Returns sublist from {@code fromIndex} to {@code toIndex} exclusively. Supports negative
85 | * indices. If {@code fromIndex} is placed before {@code toIndex}, the result list order is
86 | * descending.
87 | *
88 | * {@code
89 | * ImmutableList list = getList(); // [1, 2, 3, 4, 5, 6]
90 | * list.slice(1, 3); // [2, 3]
91 | * list.slice(-3, -1); // [4, 5]
92 | * list.slice(5, 1); // [6, 5, 4, 3]
93 | * list.slice(-2, -6); // [5, 4, 3, 2]
94 | * }
95 | *
96 | * @param fromIndex start index (inclusively)
97 | * @param toIndex end index (exclusively)
98 | * @return sublist
99 | * @throws IndexOutOfBoundsException if {@code fromIndex} is out of bounds
100 | * @see ImmutableList#slice(int, int, int)
101 | */
102 | ImmutableList slice(int fromIndex, int toIndex);
103 |
104 | /**
105 | * Returns sublist from {@code fromIndex} inclusively to {@code toIndex} exclusively with the
106 | * given step size. Supports negative indices. If {@code stepSize} is negative, then the list is
107 | * to be traversed backwards.
108 | *
109 | * {@code
110 | * ImmutableList list = getList(); // [1, 2, 3, 4, 5, 6]
111 | * list.slice(0, 3, 1); // [1, 2, 3]
112 | * list.slice(-1, 2, -1); // [6, 5, 4]
113 | * list.slice(0, 6, 2); // [0, 3, 5]
114 | * }
115 | *
116 | * @param fromIndex start index (inclusively)
117 | * @param toIndex end index (exclusively)
118 | * @param stepSize the size of the step traversing
119 | * @return result sublist
120 | * @throws IndexOutOfBoundsException if {@code fromIndex} is out of bounds
121 | * @throws IllegalArgumentException if {@code stepSize} is zero
122 | * @see ImmutableList#get(int)
123 | */
124 | ImmutableList slice(int fromIndex, int toIndex, int stepSize);
125 |
126 | /**
127 | * Returns new list with elements traversed by step step size. If {@code stepSize} is negative,
128 | * the list ought to be traversed in reversed order.
129 | *
130 | * {@code
131 | * ImmutableList list = getList(); // [1, 2, 3, 4, 5, 6]
132 | * list.step(2); // [1, 3, 5]
133 | * list.step(-3); // [6, 3]
134 | * }
135 | *
136 | * @param stepSize the size of step traversing
137 | * @return stepped list
138 | * @throws IllegalArgumentException if {@code stepSize} is zero
139 | * @see ImmutableList#step(int, int)
140 | */
141 | default ImmutableList step(int stepSize) {
142 | if (stepSize == 0) {
143 | throw new IllegalArgumentException("Step size cannot be 0");
144 | }
145 | if (stepSize > 0) {
146 | return step(0, stepSize);
147 | } else {
148 | return step(-1, stepSize);
149 | }
150 | }
151 |
152 | /**
153 | * Returns sublist traversed with given step starting from the given index.
154 | *
155 | * {@code
156 | * ImmutableList list = createList(); // [1, 2, 3, 4, 5, 6, 7]
157 | * ImmutableList newOne = list.step(0, 2) // [1, 3, 5, 7]
158 | * ImmutableList newTwo = list.step(1, 3) // [2, 5]
159 | * }
160 | *
161 | * Step size might be negative as well. It means backwards traversing.
162 | *
163 | * {@code
164 | * ImmutableList list = createList(); [1, 2, 3, 4, 5, 6, 7]
165 | * ImmutableList newOne = list.step(-1, -2) // [7, 5, 3, 1]
166 | * }
167 | *
168 | * @param fromIndex start index (might be negative)
169 | * @param stepSize the size of the step traversing
170 | * @return stepped list
171 | * @throws IndexOutOfBoundsException if fromIndex is out of bounds
172 | * @throws IllegalArgumentException if stepSize is zero
173 | * @see ImmutableList#get(int)
174 | */
175 | ImmutableList step(int fromIndex, int stepSize);
176 |
177 | /**
178 | * Zips current list with provided list and returns pairs, where key contains the element from the
179 | * current list and value contains the element from the provided list.
180 | *
181 | * {@code
182 | * ImmutableList people = getPeople();
183 | * ImmutableList jobs = getJobs();
184 | *
185 | * ImmutableList> zipped =
186 | * people.zipWith(jobs);
187 | * }
188 | * If lists have different length, then at some position the key or the value will become
189 | * null.
190 | *
191 | * @param list provided list
192 | * @param the type of the content of the provided list
193 | * @return new list
194 | * @throws NullPointerException if {@code list} is null
195 | */
196 | ImmutableList> zipWith(ImmutableList list);
197 |
198 | /**
199 | * Returns two adjacent elements of the list as pairs. If list has length less then two, returns
200 | * empty list.
201 | *
202 | * @return new list
203 | */
204 | ImmutableList> zipWithNext();
205 |
206 | /**
207 | * {@inheritDoc}
208 | */
209 | @Override
210 | ImmutableList concatWith(Iterable iterable);
211 |
212 | /**
213 | * {@inheritDoc}
214 | */
215 | @Override
216 | ImmutableList map(Function super T, ? extends R> mapper);
217 |
218 | /**
219 | * {@inheritDoc}
220 | */
221 | @Override
222 | ImmutableList flatMap(Function super T, ? extends Iterable> mapper);
223 |
224 | /**
225 | * {@inheritDoc}
226 | */
227 | @Override
228 | ImmutableList filter(Predicate super T> predicate);
229 |
230 | /**
231 | * Maps the content of the list from one type to another. Mapper accepts current index and current
232 | * value.
233 | *
234 | * @param mapper mapping function
235 | * @param the result type
236 | * @return new list
237 | * @throws NullPointerException if {@code mapper} is null
238 | */
239 | ImmutableList mapIndexed(BiFunction mapper);
240 |
241 | /**
242 | * This method has exactly the same behaviour as {@link ImmutableList#flatMap(Function)}, but
243 | * mapper accepts two arguments. The first is the current index and the second is the current
244 | * value.
245 | *
246 | * @param mapper mapping function, that returns {@link Iterable}
247 | * @param the type of the return list
248 | * @return new list
249 | * @throws NullPointerException if {@code mapper} is null
250 | * @see ImmutableList#flatMap(Function)
251 | */
252 | ImmutableList flatMapIndexed(BiFunction> mapper);
253 |
254 | /**
255 | * Returns new list which values match provided predicate.
256 | *
257 | * @param predicate predicate to apply to each element to determine if it should be included. The
258 | * first argument is the current index and the second one is the current value
259 | * @return new list
260 | * @throws NullPointerException if {@code predicate} is null
261 | */
262 | ImmutableList filterIndexed(BiPredicate predicate);
263 |
264 | /**
265 | * Traverses each element like {@link Iterable#forEach(Consumer)}, but consumer accepts two
266 | * arguments. The first arg is the current index and the second one is the current value.
267 | *
268 | * @param action consumer
269 | * @throws NullPointerException if {@code action} is null
270 | */
271 | void forEachIndexed(BiConsumer action);
272 |
273 | /**
274 | * Sorts the list and returns the new one.
275 | *
276 | * @param comparator comparator, which defines the sort order
277 | * @return new sorted list
278 | * @throws NullPointerException if {@code comparator} is null
279 | */
280 | ImmutableList sorted(Comparator super T> comparator);
281 |
282 | /**
283 | * Returns the first {@code size} elements.
If {@code size} is bigger than the size of the
284 | * list, then returns the list itself.
If {@code size} is zero, then returns an empty list.
285 | *
286 | * @param size the max size of new list, starting from the beginning
287 | * @return new list
288 | * @throws IllegalArgumentException if {@code size} is less than zero
289 | */
290 | ImmutableList limit(int size);
291 |
292 | /**
293 | * Skips the first {@code size} elements and returns remaining as a new list.
if {@code size}
294 | * is bigger than the size of the list, then returns an empty list.
if {@code size} if zero,
295 | * then return the list itself.
296 | *
297 | * @param size the count of the elements that must be skipped from the beginning
298 | * @return new list
299 | * @throws IllegalArgumentException if {@code size} is less than zero
300 | */
301 | ImmutableList skip(int size);
302 |
303 | /**
304 | * Reverses the list and returns its copy.
305 | *
306 | * @return reversed list
307 | */
308 | default ImmutableList reversed() {
309 | return step(-1);
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/src/main/java/com/kirekov/juu/collection/immutable/ImmutableMap.java:
--------------------------------------------------------------------------------
1 | package com.kirekov.juu.collection.immutable;
2 |
3 | import com.kirekov.juu.lambda.TriFunction;
4 | import java.util.Map;
5 | import java.util.Objects;
6 | import java.util.function.BiConsumer;
7 |
8 | /**
9 | * Defines an immutable map. Unlike native {@link Map} this interface does not have any methods that
10 | * can mutate its content. So it can be safely injected to any methods or objects.
11 | *
12 | * It is strongly recommended to instantiate this map with only immutable keys and values in
13 | * order not to face with some unexpected mutations.
14 | *
15 | * @param the type of the key
16 | * @param the type of the value
17 | * @see Map
18 | * @since 1.0
19 | */
20 | public interface ImmutableMap {
21 |
22 | /**
23 | * Gets size of the map.
24 | *
25 | * @return size of the map (keys count)
26 | */
27 | int size();
28 |
29 | /**
30 | * Whether the map is empty.
31 | *
32 | * @return true if size is zero, otherwise false
33 | */
34 | default boolean isEmpty() {
35 | return size() == 0;
36 | }
37 |
38 | /**
39 | * Whether the map is NOT empty.
40 | *
41 | * @return true if size is not zero, otherwise false
42 | */
43 | default boolean isNotEmpty() {
44 | return !isEmpty();
45 | }
46 |
47 | /**
48 | * Whether the map contains the given {@code key}.
49 | *
50 | * @param key the key whose presence is to be tested
51 | * @return true if map contains key, otherwise false
52 | */
53 | boolean containsKey(Object key);
54 |
55 | /**
56 | * Whether the map NOT contains the given {@code key}.
57 | *
58 | * @param key the key whose presence is to be tested
59 | * @return true if map does not contain key, otherwise false
60 | */
61 | default boolean notContainsKey(Object key) {
62 | return !containsKey(key);
63 | }
64 |
65 | /**
66 | * Whether the map contains the given {@code value}.
67 | *
68 | * @param value the value whose presence is to be tested
69 | * @return true if map contains value, otherwise false
70 | */
71 | boolean containsValue(Object value);
72 |
73 | /**
74 | * Whether the map NOT contains the given {@code value}.
75 | *
76 | * @param value the value whose presence is to be tested
77 | * @return true if map does not contain value, otherwise false
78 | */
79 | default boolean notContainsValue(Object value) {
80 | return !containsValue(value);
81 | }
82 |
83 | /**
84 | * Whether the map contains the given {@code pair}.
85 | *
86 | * @param pair the pair whose presence is to be tested
87 | * @return true if map contains pair, otherwise false
88 | * @throws NullPointerException if {@code pair} is null
89 | */
90 | default boolean containsPair(Pair pair) {
91 | Objects.requireNonNull(pair);
92 | return pairSet().contains(pair);
93 | }
94 |
95 | /**
96 | * Whether the map NOT contains the given {@code pair}.
97 | *
98 | * @param pair the value whose presence is to be tested
99 | * @return true if map does not contain pair, otherwise false
100 | * @throws NullPointerException if {@code pair} is null
101 | */
102 | default boolean notContainsPair(Pair pair) {
103 | Objects.requireNonNull(pair);
104 | return !containsPair(pair);
105 | }
106 |
107 | /**
108 | * Concatenates the current map with the given one. In case of occurring the same keys in two maps
109 | * the value from the given map will be added to the final result.
110 | *
111 | * @param map the map whose pairs will be added to the current one
112 | * @return new map with merged pairs
113 | * @throws NullPointerException if {@code map} is null
114 | * @see ImmutableMap#concatWith(ImmutableMap, TriFunction)
115 | */
116 | default ImmutableMap concatWithOverride(ImmutableMap map) {
117 | return concatWith(map, (key, oldVal, newVal) -> newVal);
118 | }
119 |
120 | /**
121 | * Concatenates the current map with the given one. In case of occurring the same keys in two maps
122 | * the value from the current map will be added to final result.
123 | *
124 | * @param map the map whose pairs will be added to the current one
125 | * @return new map with merged pairs
126 | * @throws NullPointerException if {@code map} is null
127 | * @see ImmutableMap#concatWith(ImmutableMap, TriFunction)
128 | */
129 | default ImmutableMap concatWithoutOverride(ImmutableMap map) {
130 | return concatWith(map, (key, oldVal, newVal) -> oldVal);
131 | }
132 |
133 | /**
134 | * Adds all pairs of keys and values from current map and the given map to the new one and returns
135 | * new instance.
136 | *
137 | * If these two maps have any equal keys, {@code overrideBehaviour} function will be called to
138 | * resolve the conflict.
139 | *
140 | * @param mapToConcatWith the map whose pairs will be added to the current one
141 | * @param overrideBehaviour function is called every time when two same keys in two maps are
142 | * found. The first param is the key. The second param is the value from
143 | * the current map. The third param is the value from the given map.
144 | * Function returns the value that will be added to the final result.
145 | * @return new map with merged pairs
146 | * @throws NullPointerException if {@code map} is null or {@code overrideBehaviour} is null
147 | */
148 | default ImmutableMap concatWith(ImmutableMap mapToConcatWith,
149 | TriFunction overrideBehaviour) {
150 | Map