(* * 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 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, equal] module Map = Caml.Map.Make (struct type nonrec t = t let compare = compare end) module Set = Caml.Set.Make (struct type nonrec t = t let compare = compare end) let make ~package ~classname = match package with Some "" -> {package= None; classname} | _ -> {package; classname} let from_string str = match String.rsplit2 str ~on:'.' with | None -> {classname= str; package= None} | Some ("", _) -> L.die InternalError "Empty package path in Java qualified classname.@." | Some (pkg, classname) -> {classname; package= Some pkg} let to_string = function | {classname; package= None} -> classname | {classname; package= Some pkg} -> String.concat ~sep:"." [pkg; classname] let pp fmt = function | {classname; package= None} -> F.pp_print_string fmt classname | {classname; package= Some pkg} -> F.fprintf fmt "%s.%s" pkg classname let package {package} = package let classname {classname} = classname let is_int s = try ignore (int_of_string s) ; true with Failure _ -> false let get_outer_class_name {package; classname} = String.rsplit2 classname ~on:'$' |> Option.map ~f:(fun (outer, _) -> {package; classname= outer}) (* Strips $ 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 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 (* Strips everything after $Lambda$ (if it is a lambda-class), and returns the result string together with if it was stripped *) let strip_lambda_if_present classname = match String.substr_index classname ~pattern:"$Lambda$" with | Some index -> (String.prefix classname index, true) | None -> (classname, false) (* Anonymous classes have two forms: - classic anonymous classes: suffixes in form of $. - classes corresponding to lambda-expressions: they are manifested as $Lambda$. - two forms above nested inside each other. Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass). In general case anonymous class name looks something like Class$NestedClass$1$17$5$Lambda$_1_2, and we need to return Class$NestedClass *) let get_user_defined_class_if_anonymous_inner {package; classname} = let without_lambda, was_lambda_stripped = strip_lambda_if_present classname in let outer_class_name, nesting_level = strip_anonymous_suffixes_if_present without_lambda in let was_stripped = was_lambda_stripped || nesting_level > 0 in if was_stripped 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 Option.exists ~f:Config.java_package_is_external package let pp_with_verbosity ~verbose fmt t = if verbose then pp fmt t else F.pp_print_string fmt (classname t)