1 /*
2  * Copyright (C) 2019 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.extension;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.NoSuchElementException;
24 import java.util.Optional;
25 import java.util.stream.Collector;
26 import javax.annotation.Nullable;
27 
28 /**
29  * A copy of {@link com.google.common.collect.MoreCollectors} to avoid issues with the '-android'
30  * variant of Guava. See b/68008628
31  */
32 public final class DaggerCollectors {
33 
34   private static final Collector<Object, ?, Optional<Object>> TO_OPTIONAL =
35       Collector.of(
36           ToOptionalState::new,
37           ToOptionalState::add,
38           ToOptionalState::combine,
39           ToOptionalState::getOptional,
40           Collector.Characteristics.UNORDERED);
41 
42   /**
43    * A collector that converts a stream of zero or one elements to an {@code Optional}. The returned
44    * collector throws an {@code IllegalArgumentException} if the stream consists of two or more
45    * elements, and a {@code NullPointerException} if the stream consists of exactly one element,
46    * which is null.
47    */
48   @SuppressWarnings("unchecked")
toOptional()49   public static <T> Collector<T, ?, Optional<T>> toOptional() {
50     return (Collector) TO_OPTIONAL;
51   }
52 
53   private static final Object NULL_PLACEHOLDER = new Object();
54 
55   private static final Collector<Object, ?, Object> ONLY_ELEMENT =
56       Collector.of(
57           ToOptionalState::new,
58           (state, o) -> state.add((o == null) ? NULL_PLACEHOLDER : o),
59           ToOptionalState::combine,
60           state -> {
61             Object result = state.getElement();
62             return (result == NULL_PLACEHOLDER) ? null : result;
63           },
64           Collector.Characteristics.UNORDERED);
65 
66   /**
67    * A collector that takes a stream containing exactly one element and returns that element. The
68    * returned collector throws an {@code IllegalArgumentException} if the stream consists of two or
69    * more elements, and a {@code NoSuchElementException} if the stream is empty.
70    */
71   @SuppressWarnings("unchecked")
onlyElement()72   public static <T> Collector<T, ?, T> onlyElement() {
73     return (Collector) ONLY_ELEMENT;
74   }
75 
76   private static final class ToOptionalState {
77     static final int MAX_EXTRAS = 4;
78 
79     @Nullable Object element;
80     @Nullable List<Object> extras;
81 
ToOptionalState()82     ToOptionalState() {
83       element = null;
84       extras = null;
85     }
86 
multiples(boolean overflow)87     IllegalArgumentException multiples(boolean overflow) {
88       StringBuilder sb =
89           new StringBuilder().append("expected one element but was: <").append(element);
90       for (Object o : extras) {
91         sb.append(", ").append(o);
92       }
93       if (overflow) {
94         sb.append(", ...");
95       }
96       sb.append('>');
97       throw new IllegalArgumentException(sb.toString());
98     }
99 
add(Object o)100     void add(Object o) {
101       checkNotNull(o);
102       if (element == null) {
103         this.element = o;
104       } else if (extras == null) {
105         extras = new ArrayList<>(MAX_EXTRAS);
106         extras.add(o);
107       } else if (extras.size() < MAX_EXTRAS) {
108         extras.add(o);
109       } else {
110         throw multiples(true);
111       }
112     }
113 
combine(ToOptionalState other)114     ToOptionalState combine(ToOptionalState other) {
115       if (element == null) {
116         return other;
117       } else if (other.element == null) {
118         return this;
119       } else {
120         if (extras == null) {
121           extras = new ArrayList<>();
122         }
123         extras.add(other.element);
124         if (other.extras != null) {
125           this.extras.addAll(other.extras);
126         }
127         if (extras.size() > MAX_EXTRAS) {
128           extras.subList(MAX_EXTRAS, extras.size()).clear();
129           throw multiples(true);
130         }
131         return this;
132       }
133     }
134 
getOptional()135     Optional<Object> getOptional() {
136       if (extras == null) {
137         return Optional.ofNullable(element);
138       } else {
139         throw multiples(false);
140       }
141     }
142 
getElement()143     Object getElement() {
144       if (element == null) {
145         throw new NoSuchElementException();
146       } else if (extras == null) {
147         return element;
148       } else {
149         throw multiples(false);
150       }
151     }
152   }
153 
DaggerCollectors()154   private DaggerCollectors() {}
155 }
156