JarIndex sourceIndex = new JarIndex();
sourceIndex.indexJar( sourceJar, false );
System.out.println( "Indexing dest jar..." );
JarIndex destIndex = new JarIndex();
destIndex.indexJar( destJar, false );
TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex );
TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex );
// compute the matching
ClassMatching matching = computeMatching( sourceIndex, sourceLoader, destIndex, destLoader );
Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
// get all the obf class names used in the mappings
Set<String> usedClassNames = mappings.getAllObfClassNames();
Set<String> allClassNames = Sets.newHashSet();
for( ClassEntry classEntry : sourceIndex.getObfClassEntries() )
{
allClassNames.add( classEntry.getName() );
}
usedClassNames.retainAll( allClassNames );
System.out.println( "Used " + usedClassNames.size() + " classes in the mappings" );
// probabilistically match the non-uniquely-matched source classes
for( Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values() )
{
ClassIdentity sourceClass = entry.getKey();
List<ClassIdentity> destClasses = entry.getValue();
// skip classes that are uniquely matched
if( destClasses.size() == 1 )
{
continue;
}
// skip classes that aren't used in the mappings
if( !usedClassNames.contains( sourceClass.getClassEntry().getName() ) )
{
continue;
}
System.out.println( "No exact match for source class " + sourceClass.getClassEntry() );
// find the closest classes
Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
for( ClassIdentity c : destClasses )
{
scoredMatches.put( sourceClass.getMatchScore( c ), c );
}
List<Integer> scores = new ArrayList<Integer>( scoredMatches.keySet() );
Collections.sort( scores, Collections.reverseOrder() );
printScoredMatches( sourceClass.getMaxMatchScore(), scores, scoredMatches );
// does the best match have a non-zero score and the same name?
int bestScore = scores.get( 0 );
Collection<ClassIdentity> bestMatches = scoredMatches.get( bestScore );
if( bestScore > 0 && bestMatches.size() == 1 )
{
ClassIdentity bestMatch = bestMatches.iterator().next();
if( bestMatch.getClassEntry().equals( sourceClass.getClassEntry() ) )
{
// use it
System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName() );
destClasses.clear();
destClasses.add( bestMatch );
}
}
}
// group the matching into unique and non-unique matches
BiMap<String,String> matchedClassNames = HashBiMap.create();
Set<String> unmatchedSourceClassNames = Sets.newHashSet();
for( String className : usedClassNames )
{
// is there a match for this class?
Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get( className );
ClassIdentity sourceClass = entry.getKey();
List<ClassIdentity> matches = entry.getValue();
if( matches.size() == 1 )
{
// unique match! We're good to go!
matchedClassNames.put(
sourceClass.getClassEntry().getName(),
matches.get( 0 ).getClassEntry().getName()
);
}
else
{
// no match, check the fallback matching
String fallbackMatch = fallbackMatching.get( className );
if( fallbackMatch != null )
{
matchedClassNames.put(
sourceClass.getClassEntry().getName(),
fallbackMatch
);
}
else
{
unmatchedSourceClassNames.add( className );
}
}
}
// report unmatched classes
if( !unmatchedSourceClassNames.isEmpty() )
{
System.err.println( "ERROR: there were unmatched classes!" );
for( String className : unmatchedSourceClassNames )
{
System.err.println( "\t" + className );
}
return;
}
// get the class name changes from the matched class names
Map<String,String> classChanges = Maps.newHashMap();
for( Map.Entry<String,String> entry : matchedClassNames.entrySet() )
{
if( !entry.getKey().equals( entry.getValue() ) )
{
classChanges.put( entry.getKey(), entry.getValue() );
System.out.println( String.format( "Class change: %s -> %s", entry.getKey(), entry.getValue() ) );
/* DEBUG
System.out.println( String.format( "\n%s\n%s",
new ClassIdentity( sourceLoader.loadClass( entry.getKey() ), null, sourceIndex, false, false ),
new ClassIdentity( destLoader.loadClass( entry.getValue() ), null, destIndex, false, false )
) );
*/
}
}
// sort the changes so classes are renamed in the correct order
// ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap();
int numChangesLeft = classChanges.size();
while( !classChanges.isEmpty() )
{
Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator();
while( iter.hasNext() )
{
Map.Entry<String,String> entry = iter.next();
if( classChanges.get( entry.getValue() ) == null )
{
orderedClassChanges.put( entry.getKey(), entry.getValue() );
iter.remove();
}
}
// did we remove any changes?
if( numChangesLeft - classChanges.size() > 0 )
{
// keep going
numChangesLeft = classChanges.size();
}
else
{
// can't sort anymore. There must be a loop
break;
}
}
if( classChanges.size() > 0 )
{
throw new Error( String.format( "Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size() ) );
}
// convert the mappings in the correct class order
for( Map.Entry<String,String> entry : orderedClassChanges.entrySet() )
{
mappings.renameObfClass( entry.getKey(), entry.getValue() );
}
// check the method matches
System.out.println( "Checking methods..." );
for( ClassMapping classMapping : mappings.classes() )
{
ClassEntry classEntry = new ClassEntry( classMapping.getObfName() );
for( MethodMapping methodMapping : classMapping.methods() )
{
// skip constructors
if( methodMapping.getObfName().equals( "<init>" ) )
{
continue;
}
MethodEntry methodEntry = new MethodEntry(
classEntry,
methodMapping.getObfName(),
methodMapping.getObfSignature()
);
if( !destIndex.containsObfBehavior( methodEntry ) )
{
System.err.println( "WARNING: method doesn't match: " + methodEntry );
// show the available methods
System.err.println( "\tAvailable dest methods:" );
CtClass c = destLoader.loadClass( classMapping.getObfName() );
for( CtBehavior behavior : c.getDeclaredBehaviors() )
{
MethodEntry declaredMethodEntry = new MethodEntry(
new ClassEntry( classMapping.getObfName() ),
behavior.getName(),