/*******************************************************************************
* Copyright 2014,
* Luis Pina <luis@luispina.me>,
* Michael Hicks <mwh@cs.umd.edu>
*
* This file is part of Rubah.
*
* Rubah is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Rubah is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Rubah. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package rubah.tools;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Method;
import rubah.framework.Namespace;
import rubah.runtime.Version;
import rubah.update.change.Change;
import rubah.update.change.ChangeSet;
import rubah.update.change.ChangeType;
import rubah.update.change.ClassChange;
import rubah.update.change.detector.ChangeDetector;
import rubah.update.change.detector.ClassHierarchyChangeDetector;
import rubah.update.change.detector.FieldTypeChangeDetector;
import rubah.update.change.detector.MethodBodyChangeDetector;
import rubah.update.change.detector.MethodSignatureChangeDetector;
import rubah.update.change.mapper.ClassNameMapper;
import rubah.update.change.mapper.FieldNameMapper;
import rubah.update.change.mapper.Mapper;
import rubah.update.change.mapper.MethodNameAndSignatureMapper;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.converters.BooleanConverter;
import com.beust.jcommander.converters.FileConverter;
public class Comparator {
@Parameter(
converter=FileConverter.class,
description="Previous version descriptor",
names={"-v0","--descriptor"},
required=true)
private File previousVersionDescriptor;
@Parameter(
converter=FileConverter.class,
description="Current version descriptor",
names={"-v1","--jar"},
required=true)
private File currentVersionDescriptor;
@Parameter(
converter=BooleanConverter.class,
description="Print NO_CHANGE",
names={"-n","--no-changes"})
private boolean printNoChanges = false;
private Version v0, v1;
//TODO can this constructor be private?
public Comparator() {
// Empty
}
public Comparator(Version v0, Version v1) {
this.v0 = v0;
this.v1 = v1;
}
public static void main(String[] args) throws IOException {
Comparator c = new Comparator();
JCommander argParser = new JCommander(c);
try {
argParser.parse(args);
} catch (ParameterException e) {
System.out.println(e.getMessage());
argParser.usage();
System.exit(1);
}
Namespace defaultNameSpace = new Namespace();
c.v0 = new Version( 0,
UpdatableJarAnalyzer.readFile(c.previousVersionDescriptor, defaultNameSpace),
null);
c.v1 = new Version(1,
UpdatableJarAnalyzer.readFile(c.currentVersionDescriptor, defaultNameSpace),
c.v0);
c.compare();
}
private void compare() throws IOException {
// Create a descriptor by comparing classes
for (Entry<Clazz, ClassChange> entry : this.computeChanges(false).entrySet()) {
if (!this.printNoChanges && entry.getValue().getChangeSet().isEmpty()) {
continue;
}
System.out.println(entry.getKey() + "\t" + entry.getValue().getChangeSet());
for (Entry<Field, Change<Field>> field : entry.getValue().getFieldChanges().entrySet()) {
if (!this.printNoChanges && field.getValue().getChangeSet().isEmpty()) {
continue;
}
System.out.println("\t" + field.getKey() + "\t" + field.getValue().getChangeSet());
}
for (Entry<Method, Change<Method>> method : entry.getValue().getMethodChanges().entrySet()) {
if (!this.printNoChanges && method.getValue().getChangeSet().isEmpty()) {
continue;
}
System.out.println("\t" + method.getKey() + "\t" + method.getValue().getChangeSet());
}
}
}
public Map<Clazz, ClassChange> computeChanges(boolean v0v0) {
Map<Clazz, ClassChange> ret = new HashMap<Clazz, ClassChange>();
for (Clazz c1 : this.v1.getNamespace().getDefinedClasses()) {
ChangeSet classChangeSet = new ChangeSet();
Clazz c0 = this.mapClass(c1);
if (v0v0) {
ret.put(c1, ClassChange.v0v0(c0));
continue;
}
if (c0 == null) {
ret.put(c1, ClassChange.newClass());
continue;
}
for (ChangeDetector<Clazz> detector : this.getClassChangeDetectors()) {
detector.detectChanges(c0, c1, classChangeSet);
}
HashMap<Method, Change<Method>> methodChanges =
new HashMap<Method, Change<Method>>();
for (Method m1 : c1.getMethods()) {
Method m0 = this.mapMethod(c0, m1);
ChangeSet methodChangeSet = new ChangeSet();
if (m0 == null) {
methodChangeSet.add(ChangeType.NEW_METHOD);
methodChanges.put(m1, new Change<Method>(methodChangeSet));
continue;
}
for (ChangeDetector<Method> detector : this.getMethodChangeDetectors()) {
detector.detectChanges(m0, m1, methodChangeSet);
}
methodChanges.put(m1, new Change<Method>(methodChangeSet, m0));
}
HashMap<Field, Change<Field>> fieldChanges =
new HashMap<Field, Change<Field>>();
for (Field f1 : c1.getFields()) {
Field f0 = this.mapField(c0, f1);
ChangeSet fieldChangeSet = new ChangeSet();
if (f0 == null) {
int access = f1.getAccess();
if (Modifier.isStatic(access) && Modifier.isFinal(access)) {
fieldChangeSet.add(ChangeType.NEW_CONSTANT);
} else {
fieldChangeSet.add(ChangeType.NEW_FIELD);
}
fieldChanges.put(f1, new Change<Field>(fieldChangeSet));
continue;
}
for (ChangeDetector<Field> detector : this.getFieldChangeDetectors()) {
detector.detectChanges(f0, f1, fieldChangeSet);
}
fieldChanges.put(f1, new Change<Field>(fieldChangeSet, f0));
}
ret.put( c1, new ClassChange(classChangeSet, c0, fieldChanges, methodChanges));
}
return ret;
}
private List<ChangeDetector<Field>> getFieldChangeDetectors() {
LinkedList<ChangeDetector<Field>> ret = new LinkedList<ChangeDetector<Field>>();
ret.add(new FieldTypeChangeDetector());
return ret;
}
private List<ChangeDetector<Method>> getMethodChangeDetectors() {
LinkedList<ChangeDetector<Method>> ret = new LinkedList<ChangeDetector<Method>>();
ret.add(new MethodSignatureChangeDetector());
ret.add(new MethodBodyChangeDetector());
return ret;
}
private List<ChangeDetector<Clazz>> getClassChangeDetectors() {
LinkedList<ChangeDetector<Clazz>> ret = new LinkedList<ChangeDetector<Clazz>>();
ret.add(new ClassHierarchyChangeDetector());
return ret;
}
private Field mapField(Clazz c0, Field f1) {
for (Mapper<Field> mapper : this.getFieldMappers(c0)) {
Field f0 = mapper.map(f1);
if (f0 != null) {
return f0;
}
}
return null;
}
private Method mapMethod(Clazz c0, Method m1) {
for (Mapper<Method> mapper : this.getMethodMappers(c0)) {
Method m0 = mapper.map(m1);
if (m0 != null) {
return m0;
}
}
return null;
}
private Clazz mapClass(Clazz c1) {
for (Mapper<Clazz> mapper : this.getClassMappers()) {
Clazz c0 = mapper.map(c1);
if (c0 != null) {
return c0;
}
}
return null;
}
private List<Mapper<Field>> getFieldMappers(Clazz c0) {
LinkedList <Mapper<Field>> ret = new LinkedList<Mapper<Field>>();
ret.add(new FieldNameMapper(c0));
return ret;
}
private List<Mapper<Method>> getMethodMappers(Clazz c0) {
LinkedList <Mapper<Method>> ret = new LinkedList<Mapper<Method>>();
ret.add(new MethodNameAndSignatureMapper(c0));
return ret;
}
private List<Mapper<Clazz>> getClassMappers() {
LinkedList <Mapper<Clazz>> ret = new LinkedList<Mapper<Clazz>>();
if (this.v0 != null) {
ret.add(new ClassNameMapper(this.v0.getNamespace()));
}
return ret;
}
}