1313import com .salesforce .graph .vertex .*;
1414import java .util .*;
1515import java .util .stream .Collectors ;
16+ import org .apache .tinkerpop .gremlin .process .traversal .dsl .graph .GraphTraversal ;
1617import org .apache .tinkerpop .gremlin .process .traversal .dsl .graph .GraphTraversalSource ;
1718import org .apache .tinkerpop .gremlin .process .traversal .dsl .graph .__ ;
19+ import org .apache .tinkerpop .gremlin .structure .Vertex ;
1820
1921/**
2022 * A helper class for {@link com.salesforce.rules.UnusedMethodRule}, which tracks various elements
@@ -238,6 +240,48 @@ boolean classInheritsMatchingMethod(String definingType, String signature) {
238240 return MethodUtil .getMethodWithSignature (g , definingType , signature , true ).isPresent ();
239241 }
240242
243+ /**
244+ * Get all {@link NewObjectExpressionVertex} instances representing instantiations of an object
245+ * whose type is {@code constructedType}.
246+ */
247+ List <NewObjectExpressionVertex > getConstructionsOfType (String constructedType ) {
248+ // Start off with all NewObjectExpressionVertex instances whose declared type references the
249+ // desired type.
250+ List <NewObjectExpressionVertex > results =
251+ SFVertexFactory .loadVertices (
252+ g ,
253+ g .V ()
254+ .where (
255+ H .has (
256+ NodeType .NEW_OBJECT_EXPRESSION ,
257+ Schema .TYPE ,
258+ constructedType )));
259+
260+ // Inner types can be referenced by outer/sibling types by just the inner name rather than
261+ // the full name.
262+ // This means we need to do more, but what that "more" is depends on whether we're looking
263+ // at an inner or outer type.
264+ boolean typeIsInner = constructedType .contains ("." );
265+ if (typeIsInner ) {
266+ // If the type is inner, then we need to add inner-name-only references occurring in
267+ // other classes.
268+ String [] portions = constructedType .split ("\\ ." );
269+ String outerType = portions [0 ];
270+ String innerType = portions [1 ];
271+ GraphTraversal <Vertex , Vertex > traversal =
272+ g .V ().where (H .has (NodeType .NEW_OBJECT_EXPRESSION , Schema .TYPE , innerType ));
273+ List <NewObjectExpressionVertex > additionalResults =
274+ getAliasedInnerTypeUsages (outerType , NodeType .NEW_OBJECT_EXPRESSION , traversal );
275+ results .addAll (additionalResults );
276+ } else {
277+ // If the type isn't an inner type, then it's possible that some of the references we
278+ // found are actually
279+ // inner-name-only references to inner types. So we need to remove those.
280+ results = removeInnerTypeCollisions (results , constructedType );
281+ }
282+ return results ;
283+ }
284+
241285 /**
242286 * Get all {@link MethodCallExpressionVertex} instances representing invocations of a method
243287 * named {@code methodName} on a thing called {@code referencedType}.
@@ -262,45 +306,17 @@ List<MethodCallExpressionVertex> getInvocationsOnType(
262306 String [] portions = referencedType .split ("\\ ." );
263307 String outerType = portions [0 ];
264308 String innerType = portions [1 ];
309+ GraphTraversal <Vertex , Vertex > traversal =
310+ TraversalUtil .traverseInvocationsOf (
311+ g , new ArrayList <>(), innerType , methodName );
265312 List <MethodCallExpressionVertex > additionalResults =
266- SFVertexFactory .loadVertices (
267- g ,
268- TraversalUtil .traverseInvocationsOf (
269- g , new ArrayList <>(), innerType , methodName )
270- .where (
271- __ .or (
272- H .has (
273- NodeType .METHOD_CALL_EXPRESSION ,
274- Schema .DEFINING_TYPE ,
275- outerType ),
276- H .hasStartingWith (
277- NodeType .METHOD_CALL_EXPRESSION ,
278- Schema .DEFINING_TYPE ,
279- outerType + "." ))));
313+ getAliasedInnerTypeUsages (
314+ outerType , NodeType .METHOD_CALL_EXPRESSION , traversal );
280315 results .addAll (additionalResults );
281316 } else {
282317 // If the type isn't an inner type, then it's possible some of the references we found
283318 // are actually inner-name-only references to inner types. So we need to remove those.
284- results =
285- results .stream ()
286- .filter (
287- v -> {
288- // Convert the result's definingType into an outer type by
289- // getting everything before the first period.
290- String outerType = v .getDefiningType ().split ("\\ ." )[0 ];
291- // Get any inner classes for the outer type and cast their
292- // names to lowercase.
293- Set <String > innerClassNames =
294- getInnerClasses (outerType ).stream ()
295- .map (i -> i .getName ().toLowerCase ())
296- .collect (Collectors .toSet ());
297- // If the set lacks an entry for our referenced type, then
298- // there's no conflicting inner class and we can keep this
299- // result.
300- return !innerClassNames .contains (
301- referencedType .toLowerCase ());
302- })
303- .collect (Collectors .toList ());
319+ results = removeInnerTypeCollisions (results , referencedType );
304320 }
305321 return results ;
306322 }
@@ -371,4 +387,59 @@ Optional<BaseSFVertex> getDeclarationOfReferencedValue(MethodCallExpressionVerte
371387 // Optional and let the caller figure out what to do with it.
372388 return Optional .empty ();
373389 }
390+
391+ /**
392+ * An inner class's sibling/outer classes can reference it by its inner type alone. This method
393+ * accepts a traversal containing all potential references to the inner name, and filters for
394+ * those that occur in the sibling/outer classes and therefore refer to the inner type.
395+ *
396+ * @param outerType - The name of the outer class.
397+ * @param nodeType - The node type being targeted.
398+ * @param initialTraversal - A traversal containing all nodes of {@code nodeType} that
399+ * potentially reference the inner type.
400+ * @return - A list of {@link BaseSFVertex} instances representing the subset of {@code
401+ * initialTraversal} that are aliased inner type references.
402+ * @param <T> - An extension of {@link BaseSFVertex}.
403+ */
404+ private <T extends BaseSFVertex > List <T > getAliasedInnerTypeUsages (
405+ String outerType , String nodeType , GraphTraversal <Vertex , Vertex > initialTraversal ) {
406+ return SFVertexFactory .loadVertices (
407+ g ,
408+ initialTraversal .where (
409+ __ .or (
410+ H .has (nodeType , Schema .DEFINING_TYPE , outerType ),
411+ H .hasStartingWith (
412+ nodeType , Schema .DEFINING_TYPE , outerType + "." ))));
413+ }
414+
415+ /**
416+ * If an inner class shares the name of an outer class, then references to the inner class that
417+ * occur in its outer/sibling classes can resemble references to the outer class. This class
418+ * filters such false positives from the results of a query.
419+ *
420+ * @param unfilteredResults - The results, pre-filtering
421+ * @param referencedType - The outer type with which inner types may collide
422+ * @return - {@code unfilteredResults}, with all colliding references removed
423+ */
424+ private <T extends BaseSFVertex > List <T > removeInnerTypeCollisions (
425+ List <T > unfilteredResults , String referencedType ) {
426+ return unfilteredResults .stream ()
427+ .filter (
428+ v -> {
429+ // Convert the result's definingType into an outer type by getting
430+ // everything before the first period.
431+ String outerType = v .getDefiningType ().split ("\\ ." )[0 ];
432+ // Get any inner classes belonging to this outer type in a
433+ // case-insensitive set.
434+ Set <String > innerClassNames =
435+ CollectionUtil .newTreeSetOf (
436+ getInnerClasses (outerType ).stream ()
437+ .map (UserClassVertex ::getName )
438+ .collect (Collectors .toList ()));
439+ // If the set lacks an entry for our referenced type, then there's no
440+ // conflicting inner class, and we can keep this result.
441+ return !innerClassNames .contains (referencedType );
442+ })
443+ .collect (Collectors .toList ());
444+ }
374445}
0 commit comments