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