private String doCommit(String rootPath, JsopReader t, String revisionId, String message) {
long oldRevision = headRevId, rev = headRevId + 1;
NodeImpl root = nodeMap.getRootId().getNode(nodeMap);
NodeImpl head = root.getNode("head"), oldHead = head;
NodeImpl data = head.getNode("data");
JsopWriter diff = new JsopStream();
while (true) {
int r = t.read();
if (r == JsopTokenizer.END) {
break;
}
String path = PathUtils.concat(rootPath, t.readString());
String from = PathUtils.relativize("/", path);
switch (r) {
case '+':
t.read(':');
diff.tag('+').key(path);
if (t.matches('{')) {
NodeImpl n = NodeImpl.parse(nodeMap, t, rev);
data = data.cloneAndAddChildNode(from, false, null, n, rev);
n.append(diff, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, false);
} else {
String value = t.readRawValue().trim();
String nodeName = PathUtils.getParentPath(from);
String propertyName = PathUtils.getName(from);
if (data.getNode(nodeName).hasProperty(propertyName)) {
throw ExceptionFactory.get("Property already exists: " + propertyName);
}
data = data.cloneAndSetProperty(from, value, rev);
diff.encodedValue(value);
}
diff.newline();
break;
case '-':
diff.tag('-').value(path).newline();
if (data.exists(from) || !getRevisionDataRoot(revisionId).exists(from)) {
// this will fail if the node didn't exist
data = data.cloneAndRemoveChildNode(from, rev);
}
break;
case '^':
t.read(':');
boolean isConfigChange = from.startsWith(":root/head/config/");
String value;
if (t.matches(JsopTokenizer.NULL)) {
value = null;
diff.tag('^').key(path).value(null);
} else {
value = t.readRawValue().trim();
String nodeName = PathUtils.getParentPath(from);
String propertyName = PathUtils.getName(from);
if (isConfigChange || data.getNode(nodeName).hasProperty(propertyName)) {
diff.tag('^');
} else {
diff.tag('+');
}
diff.key(path).encodedValue(value);
}
if (isConfigChange) {
String p = PathUtils.relativize(":root/head", from);
if (!head.exists("config")) {
head = head.setChild("config", new NodeImpl(nodeMap, rev), rev);
}
head = head.cloneAndSetProperty(p, value, rev);
applyConfig(head);
} else {
data = data.cloneAndSetProperty(from, value, rev);
}
diff.newline();
break;
case '>': {
t.read(':');
diff.tag('>').key(path);
String name = PathUtils.getName(from);
String position, target, to;
boolean rename;
if (t.matches('{')) {
rename = false;
position = t.readString();
t.read(':');
target = t.readString();
t.read('}');
diff.object().key(position);
if (!PathUtils.isAbsolute(target)) {
target = PathUtils.concat(rootPath, target);
}
diff.value(target).endObject();
} else {
rename = true;
position = null;
target = t.readString();
if (!PathUtils.isAbsolute(target)) {
target = PathUtils.concat(rootPath, target);
}
diff.value(target);
}
diff.newline();
boolean before = false;
if ("last".equals(position)) {
target = PathUtils.concat(target, name);
position = null;
} else if ("first".equals(position)) {
target = PathUtils.concat(target, name);
position = null;
before = true;
} else if ("before".equals(position)) {
position = PathUtils.getName(target);
target = PathUtils.getParentPath(target);
target = PathUtils.concat(target, name);
before = true;
} else if ("after".equals(position)) {
position = PathUtils.getName(target);
target = PathUtils.getParentPath(target);
target = PathUtils.concat(target, name);
} else if (position == null) {
// move
} else {
throw ExceptionFactory.get("position: " + position);
}
to = PathUtils.relativize("/", target);
boolean inPlaceRename = false;
if (rename) {
if (PathUtils.getParentPath(from).equals(PathUtils.getParentPath(to))) {
inPlaceRename = true;
position = PathUtils.getName(from);
}
}
NodeImpl node = data.getNode(from);
if (!inPlaceRename) {
data = data.cloneAndRemoveChildNode(from, rev);
}
data = data.cloneAndAddChildNode(to, before, position, node, rev);
if (inPlaceRename) {
data = data.cloneAndRemoveChildNode(from, rev);
}
break;
}
case '*': {
// TODO is it really required?
// TODO possibly support target position notation
// TODO support copy in wrappers, index,...
t.read(':');
String target = t.readString();
diff.tag('*').key(path).value(target);
if (!PathUtils.isAbsolute(target)) {
target = PathUtils.concat(rootPath, target);
}
NodeImpl node = data.getNode(from);
String to = PathUtils.relativize("/", target);
data = data.cloneAndAddChildNode(to, false, null, node, rev);
break;
}
default:
throw ExceptionFactory.get("token: " + (char) t.getTokenType());
}
}
head = head.setChild("data", data, rev);
Revision revNode = new Revision(rev, clock.nanoTime(), diff.toString(), message);
revisionCache.put(rev, revNode);
head = revNode.store(head, new NodeImpl(nodeMap, rev));
root = root.setChild("head", head, rev);
String old = Revision.formatId(oldRevision);
NodeImpl oldRev = new NodeImpl(nodeMap, rev);