[JavaClassName] Introduce a function to strip out anonymous class suffix from a class

Summary:
We will use it in follow up diffs.

From many perspectives, if the function belongs to an anonymous class,
it is useful to know the original user-defined class.

This function makes this distinction clear.

Thanks to ngorogiannis, whos work on refactoring `Typ.name` made this module
easy enough so we can introduce unit tests!

Reviewed By: ngorogiannis

Differential Revision: D20389311

fbshipit-source-id: 408d95660
master
Mitya Lyubarskiy 5 years ago committed by Facebook GitHub Bot
parent 545e6c8802
commit 769c221826

@ -10,7 +10,11 @@ module F = Format
module L = Logging
(** invariant: if [package = Some str] then [not (String.equal str "")] *)
type t = {classname: string; package: string option} [@@deriving compare]
type t = {classname: string; package: string option} [@@deriving compare, equal]
let make ~package ~classname =
match package with Some "" -> {package= None; classname} | _ -> {package; classname}
let from_string str =
match String.rsplit2 str ~on:'.' with
@ -40,19 +44,39 @@ let package {package} = package
let classname {classname} = classname
let is_anonymous_inner_class_name {classname} =
match String.rsplit2 classname ~on:'$' with
| Some (_, s) ->
let is_int =
try
ignore (int_of_string (String.strip s)) ;
true
with Failure _ -> false
in
is_int
| None ->
false
let is_int s =
try
ignore (int_of_string s) ;
true
with Failure _ -> false
(* Strips $<int> suffixes from the class name, and return how many were stripped *)
let strip_anonymous_suffixes_if_present classname =
let rec strip_recursively classname nesting_level =
match String.rsplit2 classname ~on:'$' with
| Some (outer, suffix) when is_int (String.strip suffix) ->
(* Suffix is an integer - that was an anonymous class.
But it could be nested inside another anonymous class as well *)
strip_recursively outer (nesting_level + 1)
| _ ->
(* Suffix is not an integer or not present - not an anonymous class *)
(classname, nesting_level)
in
strip_recursively classname 0
(*
Anonymous classes have suffixes in form of $<int>; but they can be nested inside of each other.
Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass).
So in general case anonymous class name looks something like
Class$NestedClass$1$17$5, and we need to return Class$NestedClass *)
let get_user_defined_class_if_anonymous_inner {package; classname} =
let outer_class_name, nesting_level = strip_anonymous_suffixes_if_present classname in
if nesting_level > 0 then Some {package; classname= outer_class_name} else None
let is_anonymous_inner_class_name t = get_user_defined_class_if_anonymous_inner t |> is_some
let is_external_via_config t =
let package = package t in

@ -7,7 +7,9 @@
open! IStd
type t [@@deriving compare]
type t [@@deriving compare, equal]
val make : package:string option -> classname:string -> t
val from_string : string -> t
@ -28,3 +30,13 @@ val is_external_via_config : t -> bool
val is_anonymous_inner_class_name : t -> bool
(** True if it is anonymous Java class:
https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html *)
val get_user_defined_class_if_anonymous_inner : t -> t option
(** If the current class is anonymous ([is_anonymous_inner_class_name] is true), Return the
corresponding user defined (not anonymous) class this anonymous class belongs to.
In general case, BOTH anonymous classes and user-defined classes can be nested, so the most
general example looks like So in general case anonymous class name looks something like
Class$NestedClass$1$17$5. This function should return Class$NestedClass for this case.
If this is not an anonymous class, returns [None]. *)

@ -0,0 +1,92 @@
(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
open OUnit2
open JavaClassName
module F = Format
let assert_equal_to classname ~expected_package ~expected_classname =
assert_equal ~printer:JavaClassName.to_string classname
(make ~package:expected_package ~classname:expected_classname)
let assert_some = function Some a -> a | None -> assert_failure "Expected Some, got None"
let assert_none = function Some _ -> assert_failure "Expected None, got Some" | None -> ()
let test_from_string =
"test_from_string"
>:: fun _ ->
assert_equal_to
(from_string "some.package.SomeClass")
~expected_package:(Some "some.package") ~expected_classname:"SomeClass" ;
assert_equal_to (from_string "SomeClass") ~expected_package:None ~expected_classname:"SomeClass" ;
assert_equal_to
(from_string "some.package.SomeClass$NestedClass")
~expected_package:(Some "some.package") ~expected_classname:"SomeClass$NestedClass" ;
assert_equal_to
(from_string "SomeClass$NestedClass")
~expected_package:None ~expected_classname:"SomeClass$NestedClass" ;
(* anonymous classes *)
assert_equal_to
(from_string "some.package.SomeClass$1")
~expected_package:(Some "some.package") ~expected_classname:"SomeClass$1" ;
(* anonymous classes can be of nested levels *)
assert_equal_to
(from_string "some.package.SomeClass$1$3")
~expected_package:(Some "some.package") ~expected_classname:"SomeClass$1$3" ;
(* anonymous classes can be inside nested *)
assert_equal_to
(from_string "SomeClass$NestedClass$1$3")
~expected_package:None ~expected_classname:"SomeClass$NestedClass$1$3"
let test_anonymous =
"test_from_string"
>:: fun _ ->
(* If it is not an anonymous class, we expect this to return None *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass")
|> assert_none ;
get_user_defined_class_if_anonymous_inner (make ~package:None ~classname:"SomeClass")
|> assert_none ;
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$SomeNestedClass$AgainNestedClass")
|> assert_none ;
(* If it is an anonymous class, we expect this to be detected *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$17")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package") ~expected_classname:"SomeClass" ;
(* Can be several nested anonymous classes *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$17$23$1")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package") ~expected_classname:"SomeClass" ;
(* Can be nested class with anonymous class *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package") ~classname:"SomeClass$NestedClass$AgainNestedClass$17")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package")
~expected_classname:"SomeClass$NestedClass$AgainNestedClass" ;
(* Can be nested class AND several nested anonymous classes *)
get_user_defined_class_if_anonymous_inner
(make ~package:(Some "some.package")
~classname:"SomeClass$NestedClass$AgainNestedClass$17$23$1")
|> assert_some
|> assert_equal_to ~expected_package:(Some "some.package")
~expected_classname:"SomeClass$NestedClass$AgainNestedClass" ;
(* If package was empty, everything should still work *)
get_user_defined_class_if_anonymous_inner
(make ~package:None ~classname:"SomeClass$NestedClass$AgainNestedClass$17$23$1")
|> assert_some
|> assert_equal_to ~expected_package:None
~expected_classname:"SomeClass$NestedClass$AgainNestedClass"
let tests = "JavaClassNameTests" >::: [test_from_string; test_anonymous]

@ -35,6 +35,7 @@ let () =
; DifferentialTests.tests
; FileDiffTests.tests
; IListTests.tests
; JavaClassNameTests.tests
; JavaProfilerSamplesTest.tests
; LivenessTests.tests
; LRUHashtblTests.tests

Loading…
Cancel
Save