Last active
September 8, 2016 18:14
-
-
Save garuma/fc30e86e829f61752ae8696e5ebecbe3 to your computer and use it in GitHub Desktop.
A Java API differ using Java.Interop's Xamarin.Android.Tools.Bytecode library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
using Xamarin.Android.Tools.Bytecode; | |
namespace JavaApiDiff | |
{ | |
class MainClass | |
{ | |
static HashSet<string> methodBodyBlacklist = new HashSet<string> { "<clinit>", "hashCode", "toString", "valueOf", "<init>" }; | |
public static void Main (string [] args) | |
{ | |
if (args.Length != 2) { | |
PrintUsage (); | |
return; | |
} | |
var jar1 = new ClassPath (args [0]); | |
var jar2 = new ClassPath (args [1]); | |
var packages1 = jar1.GetPackages (); | |
var packages2 = jar2.GetPackages (); | |
var commonPackages = HashSetDiffing (packages1, packages2, p => p.Key, (rt, items) => ReportConsole ("packages", rt, items)); | |
foreach (var package in commonPackages) { | |
var commonTypes = HashSetDiffing (package.Item1.Value, package.Item2.Value, | |
c => c.ThisClass.Name.Value, | |
(rt, items) => ReportConsole ("classes in package " + package.Item1.Key, rt, items)); | |
foreach (var type in commonTypes) { | |
var commonMethods = HashSetDiffing (type.Item1.Methods, type.Item2.Methods, | |
m => m.Name + " :: " + m.Descriptor, | |
(rt, items) => ReportConsole ("methods in class " + type.Item1.ThisClass.Name.Value, rt, items)); | |
if (type.Item1.IsEnum) | |
continue; | |
if (!type.Item1.IsEnum) { | |
var differingMethods = GetMethodsWithDifferingBodies (commonMethods).ToList (); | |
if (differingMethods.Any ()) | |
ReportConsole ("methods in type " + type.Item1.ThisClass.Name.Value, ReportType.Changed, differingMethods); | |
} | |
} | |
} | |
} | |
static IEnumerable<Tuple<T, T>> HashSetDiffing<T, TId> (IEnumerable<T> collection1, IEnumerable<T> collection2, Func<T, TId> keyExtractor, Action<ReportType, IEnumerable<TId>> reportAction) | |
{ | |
var items1 = collection1.ToLookup (keyExtractor); | |
var items2 = collection2.ToLookup (keyExtractor); | |
var keys1 = items1.Select (i => i.Key); | |
var keys2 = items2.Select (i => i.Key); | |
var commonItems = new HashSet<TId> (keys1); | |
commonItems.IntersectWith (keys2); | |
var missingItems = new HashSet<TId> (keys1); | |
missingItems.ExceptWith (keys2); | |
if (missingItems.Any ()) | |
reportAction (ReportType.Removed, missingItems); | |
var addedItems = new HashSet<TId> (keys2); | |
addedItems.ExceptWith (keys1); | |
if (addedItems.Any ()) | |
reportAction (ReportType.Added, addedItems); | |
return commonItems.Select (ci => Tuple.Create (items1[ci].First (), items2[ci].First ())); | |
} | |
static IEnumerable<string> GetMethodsWithDifferingBodies (IEnumerable<Tuple<MethodInfo, MethodInfo>> methodPairs) | |
{ | |
foreach (var metPair in methodPairs) { | |
if (methodBodyBlacklist.Contains (metPair.Item1.Name)) | |
continue; | |
var body1 = metPair.Item1.Attributes.OfType<CodeAttribute> ().FirstOrDefault (); | |
var body2 = metPair.Item2.Attributes.OfType<CodeAttribute> ().FirstOrDefault (); | |
if (body1 == null && body2 == null) | |
continue; | |
if ((body1 == null ^ body2 == null) || body1.Code.Length != body2.Code.Length || !Enumerable.SequenceEqual (body1.Code, body2.Code)) { | |
var methodName = metPair.Item1.Name + " :: " + metPair.Item1.Descriptor; | |
yield return methodName; | |
} | |
} | |
} | |
static void ReportConsole<T> (string itemName, ReportType reportType, IEnumerable<T> items) | |
{ | |
Console.ForegroundColor = GetColorForReportType (reportType); | |
Console.WriteLine ("{0} {1}:", reportType.ToString (), itemName); | |
Console.ResetColor (); | |
foreach (var i in items) { | |
Console.Write ("\t"); | |
Console.WriteLine (i.ToString ()); | |
} | |
Console.WriteLine (); | |
} | |
static ConsoleColor GetColorForReportType (ReportType rt) | |
{ | |
switch (rt) { | |
case ReportType.Added: | |
return ConsoleColor.Green; | |
case ReportType.Removed: | |
return ConsoleColor.Red; | |
default: | |
return ConsoleColor.Cyan; | |
} | |
} | |
static void PrintUsage () | |
{ | |
Console.WriteLine ("Usage: mono JavaApiDiff jar-file-1 jar-file-2"); | |
} | |
} | |
enum ReportType | |
{ | |
Added, | |
Removed, | |
Changed | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment