• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.uber.nullaway.handlers.stream;
2 
3 /*
4  * Copyright (c) 2017 Uber Technologies, Inc.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.collect.ImmutableSet;
27 import com.google.errorprone.predicates.TypePredicate;
28 import com.google.errorprone.predicates.type.DescendantOf;
29 import com.google.errorprone.suppliers.Suppliers;
30 import java.util.ArrayList;
31 import java.util.List;
32 import javax.annotation.Nullable;
33 
34 /**
35  * Used to produce a new list of StreamTypeRecord models, where each model represents a class from a
36  * stream-based API such as RxJava.
37  *
38  * <p>This class should be used as:
39  *
40  * <p>[...] models = StreamModelBuilder.start() // Start the builder .addStreamType(...) // Add a
41  * type filter matching a stream type .withX(...) // Model the type methods ... .end();
42  */
43 public class StreamModelBuilder {
44 
45   private final List<StreamTypeRecord> typeRecords = new ArrayList<>();
46   private @Nullable TypePredicate tp = null;
47   private ImmutableSet.Builder<String> filterMethodSigs;
48   private ImmutableSet.Builder<String> filterMethodSimpleNames;
49   private ImmutableMap.Builder<String, MaplikeMethodRecord> mapMethodSigToRecord;
50   private ImmutableMap.Builder<String, MaplikeMethodRecord> mapMethodSimpleNameToRecord;
51   private ImmutableSet.Builder<String> passthroughMethodSigs;
52   private ImmutableSet.Builder<String> passthroughMethodSimpleNames;
53 
StreamModelBuilder()54   private StreamModelBuilder() {
55     // initialize here to avoid having the fields be @Nullable
56     initializeBuilders();
57   }
58 
59   /**
60    * Get an empty StreamModelBuilder.
61    *
62    * @return An empty StreamModelBuilder.
63    */
start()64   public static StreamModelBuilder start() {
65     return new StreamModelBuilder();
66   }
67 
finalizeOpenStreamTypeRecord()68   private void finalizeOpenStreamTypeRecord() {
69     if (this.tp != null) {
70       typeRecords.add(
71           new StreamTypeRecord(
72               this.tp,
73               filterMethodSigs.build(),
74               filterMethodSimpleNames.build(),
75               mapMethodSigToRecord.build(),
76               mapMethodSimpleNameToRecord.build(),
77               passthroughMethodSigs.build(),
78               passthroughMethodSimpleNames.build()));
79     }
80   }
81 
82   /**
83    * Add a stream type to our models.
84    *
85    * @param tp A type predicate matching the class/interface of the type in our stream-based API.
86    * @return This builder (for chaining).
87    */
addStreamType(TypePredicate tp)88   public StreamModelBuilder addStreamType(TypePredicate tp) {
89     finalizeOpenStreamTypeRecord();
90     this.tp = tp;
91     initializeBuilders();
92     return this;
93   }
94 
95   /**
96    * Add a stream type to our models based on the type's fully qualified name.
97    *
98    * @param fullyQualifiedName the FQN of the class/interface in our stream-based API.
99    * @return This builder (for chaining).
100    */
addStreamTypeFromName(String fullyQualifiedName)101   public StreamModelBuilder addStreamTypeFromName(String fullyQualifiedName) {
102     return this.addStreamType(new DescendantOf(Suppliers.typeFromString(fullyQualifiedName)));
103   }
104 
initializeBuilders()105   private void initializeBuilders() {
106     this.filterMethodSigs = ImmutableSet.builder();
107     this.filterMethodSimpleNames = ImmutableSet.builder();
108     this.mapMethodSigToRecord = ImmutableMap.builder();
109     this.mapMethodSimpleNameToRecord = ImmutableMap.builder();
110     this.passthroughMethodSigs = ImmutableSet.builder();
111     this.passthroughMethodSimpleNames = ImmutableSet.builder();
112   }
113 
114   /**
115    * Add a filter method to the last added stream type.
116    *
117    * @param filterMethodSig The full sub-signature (everything except the receiver type) of the
118    *     filter method.
119    * @return This builder (for chaining).
120    */
withFilterMethodFromSignature(String filterMethodSig)121   public StreamModelBuilder withFilterMethodFromSignature(String filterMethodSig) {
122     this.filterMethodSigs.add(filterMethodSig);
123     return this;
124   }
125 
126   /**
127    * Add all methods of the last stream type with the given simple name as filter methods.
128    *
129    * @param methodSimpleName The method's simple name.
130    * @return This builder (for chaining).
131    */
withFilterMethodAllFromName(String methodSimpleName)132   public StreamModelBuilder withFilterMethodAllFromName(String methodSimpleName) {
133     this.filterMethodSimpleNames.add(methodSimpleName);
134     return this;
135   }
136 
137   /**
138    * Add a model for a map method to the last added stream type.
139    *
140    * @param methodSig The full sub-signature (everything except the receiver type) of the method.
141    * @param innerMethodName The name of the inner "apply" method of the callback or functional
142    *     interface that must be passed to this method.
143    * @param argsFromStream The indexes (starting at 0, not counting the receiver) of all the
144    *     arguments to this method that receive objects from the stream.
145    * @return This builder (for chaining).
146    */
withMapMethodFromSignature( String methodSig, String innerMethodName, ImmutableSet<Integer> argsFromStream)147   public StreamModelBuilder withMapMethodFromSignature(
148       String methodSig, String innerMethodName, ImmutableSet<Integer> argsFromStream) {
149     this.mapMethodSigToRecord.put(
150         methodSig, new MaplikeMethodRecord(innerMethodName, argsFromStream));
151     return this;
152   }
153 
154   /**
155    * Add all methods of the last stream type with the given simple name as map methods.
156    *
157    * @param methodSimpleName The method's simple name.
158    * @param innerMethodName The name of the inner "apply" method of the callback or functional
159    *     interface that must be passed to this method.
160    * @param argsFromStream The indexes (starting at 0, not counting the receiver) of all the
161    *     arguments to this method that receive objects from the stream. Must be the same for all
162    *     methods with this name (else use withMapMethodFromSignature).
163    * @return This builder (for chaining).
164    */
withMapMethodAllFromName( String methodSimpleName, String innerMethodName, ImmutableSet<Integer> argsFromStream)165   public StreamModelBuilder withMapMethodAllFromName(
166       String methodSimpleName, String innerMethodName, ImmutableSet<Integer> argsFromStream) {
167     this.mapMethodSimpleNameToRecord.put(
168         methodSimpleName, new MaplikeMethodRecord(innerMethodName, argsFromStream));
169     return this;
170   }
171 
172   /**
173    * Add a passthrough method to the last added stream type.
174    *
175    * <p>A passthrough method is a method that affects the stream but doesn't change the nullability
176    * information of the elements inside the stream (e.g. in o.filter(...).sync().map(...), sync() is
177    * a passthrough method if the exact same objects that are added to the stream at the end of
178    * filter are those that will be consumed by map(...).
179    *
180    * @param passthroughMethodSig The full sub-signature (everything except the receiver type) of the
181    *     method.
182    * @return This builder (for chaining).
183    */
withPassthroughMethodFromSignature(String passthroughMethodSig)184   public StreamModelBuilder withPassthroughMethodFromSignature(String passthroughMethodSig) {
185     this.passthroughMethodSigs.add(passthroughMethodSig);
186     return this;
187   }
188 
189   /**
190    * Add all methods of the last stream type with the given simple name as passthrough methods.
191    *
192    * @param methodSimpleName The method's simple name.
193    * @return This builder (for chaining).
194    */
withPassthroughMethodAllFromName(String methodSimpleName)195   public StreamModelBuilder withPassthroughMethodAllFromName(String methodSimpleName) {
196     this.passthroughMethodSimpleNames.add(methodSimpleName);
197     return this;
198   }
199 
200   /**
201    * Add a passthrough method that uses the value to the last added stream type.
202    *
203    * <p>Like a normal passthrough method, but it takes a callback which inspects but doesn't change
204    * the elements flowing through the stream.
205    *
206    * @param passthroughMethodSig The full sub-signature (everything except the receiver type) of the
207    *     method.
208    * @param innerMethodName The name of the inner method of the callback or functional interface
209    *     that must be passed to this method.
210    * @param argsFromStream The indexes (starting at 0, not counting the receiver) of all the
211    *     arguments to this method that receive objects from the stream.
212    * @return This builder (for chaining).
213    */
withUseAndPassthroughMethodFromSignature( String passthroughMethodSig, String innerMethodName, ImmutableSet<Integer> argsFromStream)214   public StreamModelBuilder withUseAndPassthroughMethodFromSignature(
215       String passthroughMethodSig, String innerMethodName, ImmutableSet<Integer> argsFromStream) {
216     this.mapMethodSigToRecord.put(
217         passthroughMethodSig, new MaplikeMethodRecord(innerMethodName, argsFromStream));
218     this.passthroughMethodSigs.add(passthroughMethodSig);
219     return this;
220   }
221 
222   /**
223    * Add all methods of the last stream type with the given simple name as use-and-passthrough
224    * methods.
225    *
226    * @param methodSimpleName The method's simple name.
227    * @param innerMethodName The name of the inner method of the callback or functional interface
228    *     that must be passed to this method.
229    * @param argsFromStream The indexes (starting at 0, not counting the receiver) of all the
230    *     arguments to this method that receive objects from the stream. Must be the same for all
231    *     methods with this name (else use withUseAndPassthroughMethodFromSignature).
232    * @return This builder (for chaining).
233    */
withUseAndPassthroughMethodAllFromName( String methodSimpleName, String innerMethodName, ImmutableSet<Integer> argsFromStream)234   public StreamModelBuilder withUseAndPassthroughMethodAllFromName(
235       String methodSimpleName, String innerMethodName, ImmutableSet<Integer> argsFromStream) {
236     this.mapMethodSimpleNameToRecord.put(
237         methodSimpleName, new MaplikeMethodRecord(innerMethodName, argsFromStream));
238     this.passthroughMethodSimpleNames.add(methodSimpleName);
239     return this;
240   }
241 
242   /**
243    * Turn the models added to this builder into a list of StreamTypeRecord objects.
244    *
245    * @return The finalized (immutable) models.
246    */
end()247   public ImmutableList<StreamTypeRecord> end() {
248     finalizeOpenStreamTypeRecord();
249     return ImmutableList.copyOf(typeRecords);
250   }
251 }
252