001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.collections; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.EnumSet; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Objects; 031import java.util.Optional; 032import java.util.Set; 033import java.util.Spliterator; 034import java.util.Spliterators; 035import java.util.function.BiConsumer; 036import java.util.function.BiFunction; 037import java.util.function.BinaryOperator; 038import java.util.function.Consumer; 039import java.util.function.Function; 040import java.util.function.Supplier; 041import java.util.stream.Collector; 042import java.util.stream.Collectors; 043import java.util.stream.Stream; 044import java.util.stream.StreamSupport; 045 046import org.conqat.lib.commons.assertion.CCSMAssert; 047import org.conqat.lib.commons.collections.CollectionUtils.FunctionWithException; 048import org.conqat.lib.commons.equals.HashCodeUtils; 049 050import com.fasterxml.jackson.annotation.JsonProperty; 051 052/** 053 * A list for storing pairs in a specific order. 054 */ 055public class PairList<S, T> implements Serializable, Iterable<Pair<S, T>> { 056 057 /** Version used for serialization. */ 058 private static final long serialVersionUID = 1; 059 060 /** The current size. */ 061 @JsonProperty("size") 062 private int size = 0; 063 064 /** The array used for storing the S. */ 065 @JsonProperty("firstElements") 066 private Object[] firstElements; 067 068 /** The array used for storing the T. */ 069 @JsonProperty("secondElements") 070 private Object[] secondElements; 071 072 /** Constructor. */ 073 public PairList() { 074 this(16); 075 } 076 077 /** Constructor. */ 078 public PairList(int initialCapacity) { 079 if (initialCapacity < 1) { 080 initialCapacity = 1; 081 } 082 firstElements = new Object[initialCapacity]; 083 secondElements = new Object[initialCapacity]; 084 } 085 086 /** Copy constructor. */ 087 public PairList(PairList<S, T> other) { 088 this(other.size); 089 addAll(other); 090 } 091 092 /** 093 * Constructor to convert a map into a pair list. 094 */ 095 public PairList(Map<S, T> map) { 096 this(map.size()); 097 for (Entry<S, T> entry : map.entrySet()) { 098 add(entry.getKey(), entry.getValue()); 099 } 100 } 101 102 /** 103 * Creates a new pair list initialized with a single key/value pair. This is 104 * especially helpful for construction of small pair lists, as type inference 105 * reduces writing overhead. 106 */ 107 public static <S, T> PairList<S, T> from(S key, T value) { 108 PairList<S, T> result = new PairList<>(); 109 result.add(key, value); 110 return result; 111 } 112 113 /** 114 * Creates a new {@link PairList} from given {@link Pair}s. 115 */ 116 @SafeVarargs 117 public static <S, T> PairList<S, T> fromPairs(Pair<S, T> pair, Pair<S, T>... additionalPairs) { 118 PairList<S, T> result = new PairList<>(); 119 120 result.add(pair.getFirst(), pair.getSecond()); 121 for (Pair<S, T> additionalPair : additionalPairs) { 122 result.add(additionalPair.getFirst(), additionalPair.getSecond()); 123 } 124 125 return result; 126 } 127 128 /** Returns whether the list is empty. */ 129 public boolean isEmpty() { 130 return size == 0; 131 } 132 133 /** Returns the size of the list. */ 134 public int size() { 135 return size; 136 } 137 138 /** Add the given pair to the list. */ 139 public void add(S first, T second) { 140 ensureSpace(size + 1); 141 firstElements[size] = first; 142 secondElements[size] = second; 143 ++size; 144 } 145 146 /** 147 * Inserts the given element at the given index, shifting all other elements. 148 * Note that this operation is liner in the length of this list. 149 */ 150 public void insert(int index, S first, T second) { 151 ensureSpace(size + 1); 152 for (int i = size; i > index; --i) { 153 firstElements[i] = firstElements[i - 1]; 154 secondElements[i] = secondElements[i - 1]; 155 } 156 ++size; 157 firstElements[index] = first; 158 secondElements[index] = second; 159 } 160 161 /** Adds all pairs from another list. */ 162 public void addAll(PairList<S, T> other) { 163 // we have to store this in a local var, as other.size may change if 164 // other == this 165 int otherSize = other.size; 166 167 ensureSpace(size + otherSize); 168 for (int i = 0; i < otherSize; ++i) { 169 firstElements[size] = other.firstElements[i]; 170 secondElements[size] = other.secondElements[i]; 171 ++size; 172 } 173 } 174 175 /** Make sure there is space for at least the given amount of elements. */ 176 protected void ensureSpace(int space) { 177 if (space <= firstElements.length) { 178 return; 179 } 180 181 Object[] oldFirst = firstElements; 182 Object[] oldSecond = secondElements; 183 int newSize = firstElements.length * 2; 184 while (newSize < space) { 185 newSize *= 2; 186 } 187 188 firstElements = new Object[newSize]; 189 secondElements = new Object[newSize]; 190 System.arraycopy(oldFirst, 0, firstElements, 0, size); 191 System.arraycopy(oldSecond, 0, secondElements, 0, size); 192 } 193 194 /** Returns the first element at given index. */ 195 @SuppressWarnings("unchecked") 196 public S getFirst(int i) { 197 checkWithinBounds(i); 198 return (S) firstElements[i]; 199 } 200 201 /** 202 * Checks whether the given <code>i</code> is within the bounds. Throws an 203 * exception otherwise. 204 */ 205 private void checkWithinBounds(int i) { 206 if (i < 0 || i >= size) { 207 throw new IndexOutOfBoundsException("Out of bounds: " + i); 208 } 209 } 210 211 /** Sets the first element at given index. */ 212 public void setFirst(int i, S value) { 213 checkWithinBounds(i); 214 firstElements[i] = value; 215 } 216 217 /** Returns the second element at given index. */ 218 @SuppressWarnings("unchecked") 219 public T getSecond(int i) { 220 checkWithinBounds(i); 221 return (T) secondElements[i]; 222 } 223 224 /** Sets the first element at given index. */ 225 public void setSecond(int i, T value) { 226 checkWithinBounds(i); 227 secondElements[i] = value; 228 } 229 230 /** Creates a new list containing all first elements. */ 231 @SuppressWarnings("unchecked") 232 public List<S> extractFirstList() { 233 List<S> result = new ArrayList<>(size + 1); 234 for (int i = 0; i < size; ++i) { 235 result.add((S) firstElements[i]); 236 } 237 return result; 238 } 239 240 /** Returns a list view of the first elements backed directly by the array. */ 241 @SuppressWarnings("unchecked") 242 public UnmodifiableList<S> getFirstList() { 243 return (UnmodifiableList<S>) CollectionUtils.asUnmodifiable(Arrays.asList(firstElements).subList(0, size)); 244 } 245 246 /** Creates a new list containing all second elements. */ 247 @SuppressWarnings("unchecked") 248 public List<T> extractSecondList() { 249 List<T> result = new ArrayList<>(size + 1); 250 for (int i = 0; i < size; ++i) { 251 result.add((T) secondElements[i]); 252 } 253 return result; 254 } 255 256 /** Returns a list view of the second elements backed directly by the array. */ 257 @SuppressWarnings("unchecked") 258 public UnmodifiableList<T> getSecondList() { 259 return (UnmodifiableList<T>) CollectionUtils.asUnmodifiable(Arrays.asList(secondElements).subList(0, size)); 260 } 261 262 /** 263 * Swaps the pairs of this list. Is S and T are different types, this will be 264 * extremely dangerous. 265 */ 266 public void swapPairs() { 267 Object[] temp = firstElements; 268 firstElements = secondElements; 269 secondElements = temp; 270 } 271 272 /** Swaps the entries located at indexes i and j. */ 273 public void swapEntries(int i, int j) { 274 S tmp1 = getFirst(i); 275 T tmp2 = getSecond(i); 276 setFirst(i, getFirst(j)); 277 setSecond(i, getSecond(j)); 278 setFirst(j, tmp1); 279 setSecond(j, tmp2); 280 } 281 282 /** Clears this list. */ 283 public void clear() { 284 size = 0; 285 } 286 287 /** Removes the last element of the list. */ 288 public void removeLast() { 289 CCSMAssert.isTrue(size > 0, "Size must be positive!"); 290 size -= 1; 291 292 // clear elements to allow GC 293 firstElements[size] = null; 294 secondElements[size] = null; 295 } 296 297 /** 298 * Removes the element at given index. Note that this operation is liner in the 299 * length of this list. 300 */ 301 public void remove(int index) { 302 for (int i = index + 1; i < size; ++i) { 303 firstElements[i - 1] = firstElements[i]; 304 secondElements[i - 1] = secondElements[i]; 305 } 306 removeLast(); 307 } 308 309 @Override 310 public String toString() { 311 StringBuffer result = new StringBuffer(); 312 result.append('['); 313 for (int i = 0; i < size; i++) { 314 if (i != 0) { 315 result.append(','); 316 } 317 result.append('('); 318 result.append(String.valueOf(firstElements[i])); 319 result.append(','); 320 result.append(String.valueOf(secondElements[i])); 321 result.append(')'); 322 } 323 result.append(']'); 324 return result.toString(); 325 } 326 327 /** {@inheritDoc} */ 328 @Override 329 public int hashCode() { 330 int prime = 31; 331 int hash = size; 332 hash = prime * hash + HashCodeUtils.hashArrayPart(firstElements, 0, size); 333 return prime * hash + HashCodeUtils.hashArrayPart(secondElements, 0, size); 334 } 335 336 /** {@inheritDoc} */ 337 @Override 338 public boolean equals(Object obj) { 339 if (this == obj) { 340 return true; 341 } 342 if (!(obj instanceof PairList)) { 343 return false; 344 } 345 346 PairList<?, ?> other = (PairList<?, ?>) obj; 347 if (size != other.size) { 348 return false; 349 } 350 for (int i = 0; i < size; i++) { 351 if (!Objects.equals(firstElements[i], other.firstElements[i]) 352 || !Objects.equals(secondElements[i], secondElements[i])) { 353 return false; 354 } 355 } 356 return true; 357 } 358 359 /** {@inheritDoc} */ 360 @Override 361 public Iterator<Pair<S, T>> iterator() { 362 return new Iterator<Pair<S, T>>() { 363 int index = 0; 364 365 /** {@inheritDoc} */ 366 @Override 367 public boolean hasNext() { 368 return index < size; 369 } 370 371 /** {@inheritDoc} */ 372 @Override 373 public Pair<S, T> next() { 374 checkWithinBounds(index); 375 int oldIndex = index; 376 index++; 377 return createPairForIndex(oldIndex); 378 } 379 }; 380 } 381 382 /** 383 * Returns a non-parallel stream of {@link Pair}s from this list. 384 * <p> 385 * NOTE: the underlying {@link Spliterator} implementation does currently not 386 * support splitting. Thus, trying to use the returned stream in parallel 387 * execution will fail. 388 * 389 * @see PairListSpliterator#trySplit() 390 */ 391 public Stream<Pair<S, T>> stream() { 392 return StreamSupport.stream(new PairListSpliterator(), false); 393 } 394 395 /** 396 * Converts this {@link PairList} into a {@link Map}. 397 * 398 * If the first elements contain duplicates (according to 399 * {@link Object#equals(Object)}, an {@link IllegalStateException} is thrown. If 400 * the first elements may have duplicates, use {@link #toMap(BinaryOperator)} 401 * instead. 402 */ 403 public Map<S, T> toMap() { 404 Map<S, T> result = new HashMap<>(); 405 // must use explicit put, as stream collector does not support null 406 // values 407 forEach(result::put); 408 return result; 409 } 410 411 /** 412 * Converts this {@link PairList} into a {@link Map}. 413 * 414 * If the first elements contain duplicates (according to 415 * {@link Object#equals(Object)}, the respective second elements are merged 416 * using the provided merging function.. 417 */ 418 public Map<S, T> toMap(BinaryOperator<T> mergeFunction) { 419 return stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond, mergeFunction)); 420 } 421 422 /** 423 * Creates a pair from the values at the given index in this list. 424 * 425 * We suppress unchecked cast warnings since the PairList stores all elements as 426 * plain Objects. 427 */ 428 @SuppressWarnings("unchecked") 429 private Pair<S, T> createPairForIndex(int index) { 430 return new Pair<>((S) firstElements[index], (T) secondElements[index]); 431 } 432 433 /** 434 * Spliterator for the {@link PairList}. 435 */ 436 private final class PairListSpliterator implements Spliterator<Pair<S, T>> { 437 438 /** 439 * The next element to process when {@link #tryAdvance(Consumer)} is called. 440 */ 441 @JsonProperty("nextElementToProcess") 442 private int nextElementToProcess; 443 444 /** {@inheritDoc} */ 445 @Override 446 public int characteristics() { 447 // the Spliterator is not subsized since we don't support splitting 448 // at the moment 449 return Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.SIZED; 450 } 451 452 /** {@inheritDoc} */ 453 @Override 454 public long estimateSize() { 455 return size - nextElementToProcess; 456 } 457 458 /** {@inheritDoc} */ 459 @Override 460 public boolean tryAdvance(Consumer<? super Pair<S, T>> action) { 461 if (nextElementToProcess == size) { 462 return false; 463 } 464 465 action.accept(createPairForIndex(nextElementToProcess)); 466 nextElementToProcess++; 467 return true; 468 } 469 470 /** 471 * {@inheritDoc} 472 * 473 * We return <code>null</code> to indicate that this {@link Spliterator} cannot 474 * be split. If parallel execution is required, this method must be implemented 475 * and the subsized characteristic should be adhered to. 476 */ 477 @Override 478 public Spliterator<Pair<S, T>> trySplit() { 479 return null; 480 } 481 } 482 483 /** 484 * Collects a stream of pairs into a {@link PairList}. 485 */ 486 private static class PairListCollector<S, T> implements Collector<Pair<S, T>, PairList<S, T>, PairList<S, T>> { 487 488 /** {@inheritDoc} */ 489 @Override 490 public Supplier<PairList<S, T>> supplier() { 491 return PairList::new; 492 } 493 494 /** {@inheritDoc} */ 495 @Override 496 public BiConsumer<PairList<S, T>, Pair<S, T>> accumulator() { 497 return (list, pair) -> list.add(pair.getFirst(), pair.getSecond()); 498 } 499 500 /** {@inheritDoc} */ 501 @Override 502 public BinaryOperator<PairList<S, T>> combiner() { 503 return (list1, list2) -> { 504 list1.addAll(list2); 505 return list1; 506 }; 507 } 508 509 /** {@inheritDoc} */ 510 @Override 511 public Function<PairList<S, T>, PairList<S, T>> finisher() { 512 return list -> list; 513 } 514 515 /** {@inheritDoc} */ 516 @Override 517 public Set<java.util.stream.Collector.Characteristics> characteristics() { 518 return EnumSet.of(Characteristics.IDENTITY_FINISH); 519 } 520 521 } 522 523 /** 524 * Returns a collector for collecting a stream of pairs into a {@link PairList}. 525 */ 526 public static <S, T> PairListCollector<S, T> toPairList() { 527 return new PairListCollector<>(); 528 } 529 530 /** 531 * Creates a {@link PairList} that consists of pairs of values taken from the 532 * two {@link List}s. I.e. entry i in the resulting {@link PairList} will 533 * consists of the pair (a, b) where a is the entry at index i in the first 534 * collection and b is the entry at index i of the second collection. 535 * 536 * Both collections must be of the same size. The order of insertion into the 537 * new {@link PairList} is determined by the order imposed by the collections' 538 * iterators. 539 */ 540 public static <S, T> PairList<S, T> zip(List<S> firstValues, List<T> secondValues) { 541 CCSMAssert.isTrue(firstValues.size() == secondValues.size(), 542 "Can only zip together collections of the same size."); 543 PairList<S, T> result = new PairList<>(firstValues.size()); 544 Iterator<S> firstIterator = firstValues.iterator(); 545 Iterator<T> secondIterator = secondValues.iterator(); 546 while (firstIterator.hasNext()) { 547 result.add(firstIterator.next(), secondIterator.next()); 548 } 549 return result; 550 } 551 552 /** 553 * For each element pair in this list, calls the given consumer with the first 554 * and second value from the pair as the only arguments. 555 */ 556 public void forEach(BiConsumer<S, T> consumer) { 557 for (int i = 0; i < size; i++) { 558 consumer.accept(getFirst(i), getSecond(i)); 559 } 560 } 561 562 /** 563 * Returns a new {@link PairList}, where both mappers are applied to the 564 * elements of the current {@link PairList} (input for each mapper is the pair 565 * of elements at each position). 566 */ 567 public <S2, T2> PairList<S2, T2> map(BiFunction<S, T, S2> firstMapper, BiFunction<S, T, T2> secondMapper) { 568 PairList<S2, T2> result = new PairList<>(size); 569 forEach((key, value) -> result.add(firstMapper.apply(key, value), secondMapper.apply(key, value))); 570 return result; 571 } 572 573 /** 574 * Returns a new {@link PairList}, where the first elements are obtained by 575 * applying the given mapper function to the first elements of this list. 576 */ 577 public <U, E extends Exception> PairList<U, T> mapFirst(FunctionWithException<S, U, ? extends E> mapper) throws E { 578 PairList<U, T> mappedList = new PairList<>(size); 579 for (int i = 0; i < size; i++) { 580 mappedList.add(mapper.apply(getFirst(i)), getSecond(i)); 581 } 582 return mappedList; 583 } 584 585 /** 586 * Returns a new {@link PairList}, where the second elements are obtained by 587 * applying the given mapper function to the second elements of this list. 588 */ 589 public <U, E extends Exception> PairList<S, U> mapSecond(FunctionWithException<T, U, ? extends E> mapper) throws E { 590 PairList<S, U> mappedList = new PairList<>(size); 591 for (int i = 0; i < size; i++) { 592 mappedList.add(getFirst(i), mapper.apply(getSecond(i))); 593 } 594 return mappedList; 595 } 596 597 /** 598 * Filters the pairlist by testing all items against the predicate and returning 599 * a pairlist of those for which it returns true. This method does not modify 600 * the original pairlist. 601 */ 602 public PairList<S, T> filter(BiFunction<S, T, Boolean> filterPredicate) { 603 PairList<S, T> filteredList = new PairList<>(size); 604 for (int i = 0; i < size; i++) { 605 if (filterPredicate.apply(getFirst(i), getSecond(i))) { 606 filteredList.add(getFirst(i), getSecond(i)); 607 } 608 } 609 return filteredList; 610 } 611 612 /** 613 * Tests all the items against the predicate and returns true, if any of the 614 * items matched the predicate. Otherwise, returns false. 615 */ 616 public boolean anyMatch(BiFunction<S, T, Boolean> predicate) { 617 for (int i = 0; i < size; i++) { 618 if (predicate.apply(getFirst(i), getSecond(i))) { 619 return true; 620 } 621 } 622 return false; 623 } 624 625 /** Returns a reversed copy of this list. */ 626 public PairList<S, T> reverse() { 627 PairList<S, T> reversed = new PairList<>(); 628 for (int i = size() - 1; i >= 0; i--) { 629 reversed.add(getFirst(i), getSecond(i)); 630 } 631 return reversed; 632 } 633 634 /** 635 * Returns a newly allocated array containing all of the elements in this 636 * PairList as an array of Pair<S,T>s. 637 */ 638 @SuppressWarnings("unchecked") 639 public Pair<S, T>[] toArray() { 640 Pair<S, T>[] result = new Pair[size]; 641 for (int i = 0; i < size; i++) { 642 result[i] = new Pair<>((S) firstElements[i], (T) secondElements[i]); 643 } 644 return result; 645 } 646 647 /** 648 * Returns a newly allocated list containing all of the elements in this 649 * PairList as a list of Pair<S,T>s. 650 */ 651 public List<Pair<S, T>> toList() { 652 List<Pair<S, T>> result = new ArrayList<>(size); 653 for (int i = 0; i < size; i++) { 654 result.add(new Pair<>((S) firstElements[i], (T) secondElements[i])); 655 } 656 return result; 657 } 658 659 /** 660 * Sorts the PairList according to the given comparator. 661 */ 662 public void sort(Comparator<Pair<S, T>> comparator) { 663 Pair<S, T>[] sorted = this.toArray(); 664 Arrays.sort(sorted, comparator); 665 for (int i = 0; i < sorted.length; i++) { 666 setFirst(i, sorted[i].getFirst()); 667 setSecond(i, sorted[i].getSecond()); 668 } 669 } 670 671 /** 672 * Returns a new {@link PairList} with all elements from both lists in order. 673 */ 674 public static <S, T> PairList<S, T> concatenate(PairList<S, T> list1, PairList<S, T> list2) { 675 PairList<S, T> result = new PairList<>(list1.size() + list2.size()); 676 result.addAll(list1); 677 result.addAll(list2); 678 return result; 679 } 680 681 /** 682 * The empty list (immutable). This list is serializable. 683 */ 684 @SuppressWarnings("rawtypes") 685 private static final PairList EMPTY_PAIR_LIST = new PairList<Object, Object>(Collections.emptyMap()) { 686 687 /** default */ 688 private static final long serialVersionUID = 1; 689 690 /** {@inheritDoc} */ 691 @Override 692 public Iterator<Pair<Object, Object>> iterator() { 693 return Collections.emptyIterator(); 694 } 695 696 /** {@inheritDoc} */ 697 @Override 698 public Spliterator<Pair<Object, Object>> spliterator() { 699 return Spliterators.emptySpliterator(); 700 } 701 702 /** {@inheritDoc} */ 703 @Override 704 public void add(Object first, Object second) { 705 throw new UnsupportedOperationException(); 706 } 707 708 /** {@inheritDoc} */ 709 @Override 710 public void addAll(PairList<Object, Object> other) { 711 throw new UnsupportedOperationException(); 712 } 713 714 /** {@inheritDoc} */ 715 @Override 716 public List<Object> extractFirstList() { 717 return Collections.emptyList(); 718 } 719 720 /** {@inheritDoc} */ 721 @Override 722 public List<Object> extractSecondList() { 723 return Collections.emptyList(); 724 } 725 }; 726 727 /** 728 * Returns an empty list (immutable). This list is serializable. This is 729 * inspired by the Java Collections.emptyList() method. 730 * 731 * <p> 732 * This example illustrates the type-safe way to obtain an empty Pairlist: 733 * 734 * <pre> 735 * PairList<String, String> s = PairList.emptyPairList(); 736 * </pre> 737 * 738 * @param <S> 739 * key type of elements, if there were any, in the list 740 * @param <T> 741 * value type of elements, if there were any, in the list 742 * @return an empty immutable list 743 * 744 */ 745 @SuppressWarnings("unchecked") 746 public static final <S, T> PairList<S, T> emptyPairList() { 747 return EMPTY_PAIR_LIST; 748 } 749 750 /** Add the given pair to the list, if it is present. */ 751 public void addIfPresent(Optional<Pair<S, T>> pair) { 752 if (pair.isPresent()) { 753 add(pair.get().getFirst(), pair.get().getSecond()); 754 } 755 } 756 757 /** 758 * Maps a pair list to another one using separate mappers for keys and values. 759 */ 760 public <S2, T2> PairList<S2, T2> map(Function<S, S2> keyMapper, Function<T, T2> valueMapper) { 761 PairList<S2, T2> result = new PairList<>(); 762 forEach((key, value) -> result.add(keyMapper.apply(key), valueMapper.apply(value))); 763 return result; 764 } 765 766 /** 767 * Returns the index of the given object in the "first" list of this PairList or 768 * Optional.empty if none of the first objects is equal to the given object. 769 */ 770 public Optional<Integer> indexOfFirst(S firstSearchObject) { 771 for (int i = 0; i < size; i++) { 772 if (Objects.equals(firstElements[i], firstSearchObject)) { 773 return Optional.of(i); 774 } 775 } 776 return Optional.empty(); 777 } 778 779}