@@ -3,45 +3,85 @@ package io.shiftleft
33import org .slf4j .{Logger , LoggerFactory }
44
55object Implicits {
6-
76 private val logger : Logger = LoggerFactory .getLogger(Implicits .getClass)
87
9- implicit class IterableOnceDeco [T ](val iterable : IterableOnce [T ]) extends AnyVal {
10- def onlyChecked : T = {
11- if (iterable.iterator.hasNext) {
12- val res = iterable.iterator.next()
13- if (iterable.iterator.hasNext) {
14- logger.warn(" iterator was expected to have exactly one element, but it actually has more" )
8+ extension [A ](iterable : IterableOnce [A ]) {
9+
10+ /** @see {{{loneElement(hint)}}} */
11+ def loneElement : A =
12+ loneElement(hint = " " )
13+
14+ /** @return
15+ * the one and only element from an Iterable
16+ * @throws NoSuchElementException
17+ * if the Iterable is empty
18+ * @throws AssertionError
19+ * if the Iterable has more than one element
20+ */
21+ def loneElement (hint : String ): A = {
22+ lazy val hintMaybe =
23+ if (hint.isEmpty) " "
24+ else s " Hint: $hint"
25+
26+ val iter = iterable.iterator
27+ if (iter.isEmpty) {
28+ throw new NoSuchElementException (
29+ s " Iterable was expected to have exactly one element, but it is empty. $hintMaybe"
30+ )
31+ } else {
32+ val res = iter.next()
33+ if (iter.hasNext) {
34+ val collectionSizeHint = iterable.knownSize match {
35+ case - 1 => " it has more than one" // cannot be computed cheaply, i.e. without traversing the collection
36+ case knownSize => s " it has $knownSize"
37+ }
38+ throw new AssertionError (
39+ s " Iterable was expected to have exactly one element, but $collectionSizeHint. $hintMaybe"
40+ )
1541 }
1642 res
17- } else { throw new NoSuchElementException () }
43+ }
1844 }
19- }
2045
21- /** A wrapper around a Java iterator that throws a proper NoSuchElementException.
22- *
23- * Proper in this case means an exception with a stack trace. This is intended to be used as a replacement for next()
24- * on the iterators returned from TinkerPop since those are missing stack traces.
25- */
26- implicit class JavaIteratorDeco [T ](val iterator : java.util.Iterator [T ]) extends AnyVal {
27- def nextChecked : T = {
28- try {
29- iterator.next
30- } catch {
31- case _ : NoSuchElementException =>
32- throw new NoSuchElementException ()
46+ /** @see {{{loneElementOption(hint)}}} */
47+ def loneElementOption : Option [A ] =
48+ loneElementOption(hint = None )
49+
50+ /** @return
51+ * {{{Some(element)}}} if the Iterable has exactly one element, or {{{None}}} if the Iterable has zero or more
52+ * than 1 element. Note: if the lone element is {{{null}}}, this will return {{{Some(null)}}}, which is in
53+ * accordance with how {{{headOption}}} works.
54+ */
55+ def loneElementOption (hint : String | None .type = None ): Option [A ] = {
56+ val iter = iterable.iterator
57+ if (iter.isEmpty) {
58+ None
59+ } else {
60+ val result = iter.next()
61+ if (iter.hasNext) {
62+ None
63+ } else {
64+ Some (result)
65+ }
3366 }
3467 }
68+ }
3569
70+ implicit class IterableOnceDeco [T ](val iterable : IterableOnce [T ]) extends AnyVal {
71+ @ deprecated(
72+ " please use `loneElement` instead, which has a better name and will throw if the iterable has more than one element (rather than just log.warn)" ,
73+ since = " 1.7.42 (July 2025)"
74+ )
3675 def onlyChecked : T = {
37- if (iterator.hasNext) {
38- val res = iterator.next
39- if (iterator.hasNext) {
76+ if (iterable. iterator.hasNext) {
77+ val res = iterable. iterator.next()
78+ if (iterable. iterator.hasNext) {
4079 logger.warn(" iterator was expected to have exactly one element, but it actually has more" )
4180 }
4281 res
43- } else { throw new NoSuchElementException () }
82+ } else {
83+ throw new NoSuchElementException ()
84+ }
4485 }
4586 }
46-
4787}
0 commit comments