Extend lint script to emit API statistics.

Bug: 77588754
Test: manual
Change-Id: I240dba5fae1a8635a4265a1af903517f00dec54c
(cherry picked from commit daa7cf3a365c24e3a81d0df178c188b12e6d3a91)
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 70a47cf..9db3f02 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -1334,18 +1334,25 @@
             error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
 
 
+def is_interesting(clazz):
+    """Test if given class is interesting from an Android PoV."""
+
+    if clazz.pkg.name.startswith("java"): return False
+    if clazz.pkg.name.startswith("junit"): return False
+    if clazz.pkg.name.startswith("org.apache"): return False
+    if clazz.pkg.name.startswith("org.xml"): return False
+    if clazz.pkg.name.startswith("org.json"): return False
+    if clazz.pkg.name.startswith("org.w3c"): return False
+    if clazz.pkg.name.startswith("android.icu."): return False
+    return True
+
+
 def examine_clazz(clazz):
     """Find all style issues in the given class."""
 
     notice(clazz)
 
-    if clazz.pkg.name.startswith("java"): return
-    if clazz.pkg.name.startswith("junit"): return
-    if clazz.pkg.name.startswith("org.apache"): return
-    if clazz.pkg.name.startswith("org.xml"): return
-    if clazz.pkg.name.startswith("org.json"): return
-    if clazz.pkg.name.startswith("org.w3c"): return
-    if clazz.pkg.name.startswith("android.icu."): return
+    if not is_interesting(clazz): return
 
     verify_constants(clazz)
     verify_enums(clazz)
@@ -1479,6 +1486,7 @@
     # Remove all existing things so we're left with new
     for prev_clazz in prev.values():
         cur_clazz = cur[prev_clazz.fullname]
+        if not is_interesting(cur_clazz): continue
 
         sigs = { i.ident: i for i in prev_clazz.ctors }
         cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
@@ -1506,6 +1514,38 @@
         print
 
 
+def show_stats(cur, prev):
+    """Show API stats."""
+
+    stats = collections.defaultdict(int)
+    for cur_clazz in cur.values():
+        if not is_interesting(cur_clazz): continue
+
+        if cur_clazz.fullname not in prev:
+            stats['new_classes'] += 1
+            stats['new_ctors'] += len(cur_clazz.ctors)
+            stats['new_methods'] += len(cur_clazz.methods)
+            stats['new_fields'] += len(cur_clazz.fields)
+        else:
+            prev_clazz = prev[cur_clazz.fullname]
+
+            sigs = { i.ident: i for i in prev_clazz.ctors }
+            ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
+            sigs = { i.ident: i for i in prev_clazz.methods }
+            methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
+            sigs = { i.ident: i for i in prev_clazz.fields }
+            fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
+
+            if ctors + methods + fields > 0:
+                stats['extend_classes'] += 1
+                stats['extend_ctors'] += ctors
+                stats['extend_methods'] += methods
+                stats['extend_fields'] += fields
+
+    print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
+    print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
+
+
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Enforces common Android public API design \
             patterns. It ignores lint messages from a previous API level, if provided.")
@@ -1520,6 +1560,8 @@
             help="Show API changes noticed")
     parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
             help="Show API deprecations at birth")
+    parser.add_argument("--show-stats", action='store_const', const=True,
+            help="Show API stats")
     args = vars(parser.parse_args())
 
     if args['no_color']:
@@ -1539,6 +1581,14 @@
         show_deprecations_at_birth(cur, prev)
         sys.exit()
 
+    if args['show_stats']:
+        with current_file as f:
+            cur = _parse_stream(f)
+        with previous_file as f:
+            prev = _parse_stream(f)
+        show_stats(cur, prev)
+        sys.exit()
+
     with current_file as f:
         cur_fail, cur_noticed = examine_stream(f)
     if not previous_file is None: