this.store = store;
this.pathFilter = (pathFilter == null || "".equals(pathFilter)) ? "/" : pathFilter;
}
public String build() throws Exception {
final JsopBuilder buff = new JsopBuilder();
// maps (key: the target node, value: the path to the target)
// for tracking added/removed nodes; this allows us
// to detect 'move' operations
// TODO performance problem: this class uses NodeState as a hash key,
// which is not recommended because the hashCode and equals methods
// of those classes are slow
final HashMap<NodeState, String> addedNodes = new HashMap<NodeState, String>();
final HashMap<NodeState, String> removedNodes = new HashMap<NodeState, String>();
if (!PathUtils.isAncestor(path, pathFilter)
&& !path.startsWith(pathFilter)) {
return "";
}
if (before == null) {
if (after != null) {
buff.tag('+').key(path).object();
toJson(buff, after, depth);
return buff.endObject().newline().toString();
} else {
// path doesn't exist in the specified revisions
return "";
}
} else if (after == null) {
buff.tag('-');
buff.value(path);
return buff.newline().toString();
}
TraversingNodeDiffHandler diffHandler = new TraversingNodeDiffHandler(store) {
int levels = depth < 0 ? Integer.MAX_VALUE : depth;
@Override
public void propertyAdded(PropertyState after) {
String p = PathUtils.concat(getCurrentPath(), after.getName());
if (p.startsWith(pathFilter)) {
buff.tag('^').
key(p).
encodedValue(after.getEncodedValue()).
newline();
}
}
@Override
public void propertyChanged(PropertyState before, PropertyState after) {
String p = PathUtils.concat(getCurrentPath(), after.getName());
if (p.startsWith(pathFilter)) {
buff.tag('^').
key(p).
encodedValue(after.getEncodedValue()).
newline();
}
}
@Override
public void propertyDeleted(PropertyState before) {
String p = PathUtils.concat(getCurrentPath(), before.getName());
if (p.startsWith(pathFilter)) {
// since property and node deletions can't be distinguished
// using the "- <path>" notation we're representing
// property deletions as "^ <path>:null"
buff.tag('^').
key(p).
value(null).
newline();
}
}
@Override
public void childNodeAdded(String name, NodeState after) {
String p = PathUtils.concat(getCurrentPath(), name);
if (p.startsWith(pathFilter)) {
addedNodes.put(after, p);
buff.tag('+').
key(p).object();
toJson(buff, after, depth);
buff.endObject().newline();
}
}
@Override
public void childNodeDeleted(String name, NodeState before) {
String p = PathUtils.concat(getCurrentPath(), name);
if (p.startsWith(pathFilter)) {
removedNodes.put(before, p);
buff.tag('-');
buff.value(p);
buff.newline();
}
}
@Override
public void childNodeChanged(String name, NodeState before, NodeState after) {
String p = PathUtils.concat(getCurrentPath(), name);
if (PathUtils.isAncestor(p, pathFilter)
|| p.startsWith(pathFilter)) {
--levels;
if (levels >= 0) {
// recurse
super.childNodeChanged(name, before, after);
} else {
buff.tag('^');
buff.key(p);
buff.object().endObject();
buff.newline();
}
++levels;
}
}
};
diffHandler.start(before, after, path);
// check if this commit includes 'move' operations
// by building intersection of added and removed nodes
addedNodes.keySet().retainAll(removedNodes.keySet());
if (!addedNodes.isEmpty()) {
// this commit includes 'move' operations
removedNodes.keySet().retainAll(addedNodes.keySet());
// addedNodes & removedNodes now only contain information about moved nodes
// re-build the diff in a 2nd pass, this time representing moves correctly
buff.resetWriter();
// TODO refactor code, avoid duplication
diffHandler = new TraversingNodeDiffHandler(store) {
int levels = depth < 0 ? Integer.MAX_VALUE : depth;
@Override
public void propertyAdded(PropertyState after) {
String p = PathUtils.concat(getCurrentPath(), after.getName());
if (p.startsWith(pathFilter)) {
buff.tag('^').
key(p).
encodedValue(after.getEncodedValue()).
newline();
}
}
@Override
public void propertyChanged(PropertyState before, PropertyState after) {
String p = PathUtils.concat(getCurrentPath(), after.getName());
if (p.startsWith(pathFilter)) {
buff.tag('^').
key(p).
encodedValue(after.getEncodedValue()).
newline();
}
}
@Override
public void propertyDeleted(PropertyState before) {
String p = PathUtils.concat(getCurrentPath(), before.getName());
if (p.startsWith(pathFilter)) {
// since property and node deletions can't be distinguished
// using the "- <path>" notation we're representing
// property deletions as "^ <path>:null"
buff.tag('^').
key(p).
value(null).
newline();
}
}
@Override
public void childNodeAdded(String name, NodeState after) {
if (addedNodes.containsKey(after)) {
// moved node, will be processed separately
return;
}
String p = PathUtils.concat(getCurrentPath(), name);
if (p.startsWith(pathFilter)) {
buff.tag('+').
key(p).object();
toJson(buff, after, depth);
buff.endObject().newline();
}
}
@Override
public void childNodeDeleted(String name, NodeState before) {
if (addedNodes.containsKey(before)) {
// moved node, will be processed separately
return;
}
String p = PathUtils.concat(getCurrentPath(), name);
if (p.startsWith(pathFilter)) {
buff.tag('-');
buff.value(p);
buff.newline();
}
}
@Override
public void childNodeChanged(String name, NodeState before, NodeState after) {
String p = PathUtils.concat(getCurrentPath(), name);
if (PathUtils.isAncestor(p, pathFilter)
|| p.startsWith(pathFilter)) {
--levels;
if (levels >= 0) {
// recurse
super.childNodeChanged(name, before, after);
} else {
buff.tag('^');
buff.value(p);
buff.newline();
}
++levels;
}
}
};
diffHandler.start(before, after, path);
// finally process moved nodes
for (Map.Entry<NodeState, String> entry : addedNodes.entrySet()) {
buff.tag('>').
// path/to/deleted/node
key(removedNodes.get(entry.getKey())).
// path/to/added/node
value(entry.getValue()).
newline();
}
}
return buff.toString();
}