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