/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.util;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.*;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.InheritedProperties;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @author TMate Software Ltd.
* @version 1.3
*/
public class SVNSkel {
private static final int DEFAULT_BUFFER_SIZE = 1024;
public static final char TYPE_NOTHING = 0;
public static final char TYPE_SPACE = 1;
public static final char TYPE_DIGIT = 2;
public static final char TYPE_PAREN = 3;
public static final char TYPE_NAME = 4;
private static final char[] TYPES_TABLE = new char[]{
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
/* 64 */
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 3, 0, 0,
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
/* 128 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 192 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
public static char getType(byte b) {
return TYPES_TABLE[b & 0xFF];
}
public static SVNSkel parse(byte[] data) throws SVNException {
if (data == null) {
return null;
}
return parse(data, 0, data.length);
}
public static SVNSkel parse(byte[] data, int offset, int length) throws SVNException {
if (data == null || length == 0 || offset + length > data.length) {
return null;
}
ByteBuffer buffer = ByteBuffer.wrap(data, offset, length);
return parse(buffer);
}
public static SVNSkel parse(ByteBuffer buffer) throws SVNException {
if (buffer == null || !buffer.hasRemaining()) {
return null;
}
byte cur = buffer.get(buffer.position());
if (cur == '(') {
return parseList(buffer);
}
if (getType(cur) == TYPE_NAME) {
return parseImplicitAtom(buffer);
}
return parseExplicitAtom(buffer);
}
public static SVNSkel parseList(ByteBuffer buffer) throws SVNException {
if (buffer == null || !buffer.hasRemaining()) {
return null;
}
if (buffer.get() != '(') {
return null;
}
if (!buffer.hasRemaining()) {
return null;
}
SVNSkel list = createEmptyList();
while (true) {
byte cur = 0;
while (buffer.hasRemaining()) {
cur = buffer.get();
if (getType(cur) != TYPE_SPACE) {
break;
}
}
if (cur == ')') {
break;
}
if (!buffer.hasRemaining()) {
//cur is not ')' and the buffer has no more character, SVNSkel has incorrect format
return null;
}
buffer = unread(buffer, 1);
SVNSkel element = parse(buffer);
if (element == null) {
return null;
}
list.appendChild(element);
}
return list;
}
public static SVNSkel parseImplicitAtom(ByteBuffer buffer) {
if (buffer == null || !buffer.hasRemaining()) {
return null;
}
if (getType(buffer.get(buffer.position())) != TYPE_NAME) {
return null;
}
int start = buffer.position();
while (buffer.hasRemaining()) {
byte cur = buffer.get();
if (getType(cur) == TYPE_SPACE || getType(cur) == TYPE_PAREN) {
buffer = unread(buffer, 1);
break;
}
}
return createAtom(buffer.array(), buffer.arrayOffset() + start, buffer.position() - start);
}
public static SVNSkel parseExplicitAtom(ByteBuffer buffer) {
if (buffer == null || !buffer.hasRemaining()) {
return null;
}
int size = parseSize(buffer, buffer.remaining());
if (size < 0) {
return null;
}
if (!buffer.hasRemaining() || getType(buffer.get()) != TYPE_SPACE) {
return null;
}
int start = buffer.arrayOffset() + buffer.position();
buffer.position(buffer.position() + size);
return createAtom(buffer.array(), start, size);
}
private static SVNSkel createAtom(SVNPropertyValue propertyValue) {
if (propertyValue != null && propertyValue.getString() != null) {
return createAtom(propertyValue.getString());
} else if (propertyValue != null && propertyValue.getBytes() != null) {
return createAtom(propertyValue.getBytes());
} else {
return createAtom("");
}
}
public static SVNSkel createAtom(String str) {
if (str == null) {
return null;
}
byte[] data;
try {
data = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
data = str.getBytes();
}
return new SVNSkel(data);
}
public static SVNSkel createAtom(byte[] data) {
if (data == null) {
return null;
}
return createAtom(data, 0, data.length);
}
public static SVNSkel createAtom(byte[] data, int offset, int length) {
if (data == null) {
return null;
}
byte[] raw = new byte[length];
System.arraycopy(data, offset, raw, 0, length);
return new SVNSkel(raw);
}
public static SVNSkel createEmptyList() {
return new SVNSkel();
}
public static SVNSkel createPropList(Map<String, SVNPropertyValue> props) throws SVNException {
SVNSkel list = createEmptyList();
if (props == null) {
return list;
}
for(String propertyName : props.keySet()) {
SVNSkel name = createAtom(propertyName);
SVNPropertyValue pv = props.get(propertyName);
SVNSkel value = createAtom(pv);
list.prepend(value);
list.prepend(name);
}
if (!list.isValidPropList()) {
error("proplist");
}
return list;
}
final private byte[] myRawData;
final private List<SVNSkel> myList;
private SVNSkel myNext;
protected SVNSkel(byte[] data) {
myRawData = data;
myList = null;
myNext = null;
}
protected SVNSkel() {
myRawData = null;
myList = new ArrayList<SVNSkel>();
}
public boolean isAtom() {
return myList == null;
}
public byte[] getData() {
return myRawData;
}
public List<SVNSkel> getList() {
return Collections.unmodifiableList(myList);
}
public SVNSkel first() {
if (myList != null && !myList.isEmpty()) {
return myList.get(0);
}
return null;
}
public SVNSkel next() {
return myNext;
}
public SVNSkel getChild(int i) throws SVNException {
if (isAtom()) {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.FS_MALFORMED_SKEL, "Unable to get a child from atom");
SVNErrorManager.error(error, SVNLogType.DEFAULT);
}
return (SVNSkel) myList.get(i);
}
public void appendChild(SVNSkel child) throws SVNException {
if (isAtom()) {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.FS_MALFORMED_SKEL, "Unable to add a child to atom");
SVNErrorManager.error(error, SVNLogType.DEFAULT);
}
if (!myList.isEmpty()) {
myList.get(myList.size() - 1).myNext = child;
}
myList.add(child);
}
public void prepend(SVNSkel child) throws SVNException {
if (isAtom()) {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.FS_MALFORMED_SKEL, "Unable to add a child to atom");
SVNErrorManager.error(error, SVNLogType.DEFAULT);
}
if (!myList.isEmpty()) {
child.myNext = myList.get(0);
}
myList.add(0, child);
}
public void prependString(String str) throws SVNException {
SVNSkel skel = SVNSkel.createAtom(str);
prepend(skel);
}
public void prependPropertyValue(SVNPropertyValue propertyValue) throws SVNException{
SVNSkel skel = SVNSkel.createAtom(propertyValue);
prepend(skel);
}
public void prependPath(File path) throws SVNException {
String str = path != null ? SVNFileUtil.getFilePath(path) : "";
prependString(str);
}
public int getListSize() {
if (isAtom()) {
return -1;
}
return myList.size();
}
public String getValue() {
if (isAtom()) {
String str;
try {
str = new String(getData(), "UTF-8");
} catch (UnsupportedEncodingException e) {
str = new String(getData());
}
return str;
}
return null;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
if (isAtom()) {
buffer.append("[");
buffer.append(getValue());
buffer.append("]");
} else {
buffer.append("(");
for (Iterator<SVNSkel> iterator = myList.iterator(); iterator.hasNext();) {
SVNSkel element = (SVNSkel) iterator.next();
buffer.append(element.toString());
}
buffer.append(")");
}
return buffer.toString();
}
public boolean contentEquals(String str) {
if (!isAtom()) {
return false;
}
String value = getValue();
return value.equals(str);
}
public boolean containsAtomsOnly() {
if (isAtom()) {
return false;
}
for (Iterator<SVNSkel> iterator = myList.iterator(); iterator.hasNext();) {
SVNSkel element = (SVNSkel) iterator.next();
if (!element.isAtom()) {
return false;
}
}
return true;
}
public boolean isValidPropList() {
int length = getListSize();
if (length >= 0 && (length & 1) == 0) {
return containsAtomsOnly();
}
return false;
}
public Map<String, byte[]> parsePropList() throws SVNException {
if (!isValidPropList()) {
error("proplist");
}
@SuppressWarnings("unchecked")
Map<String, byte[]> props = new SVNHashMap();
for (Iterator<SVNSkel> iterator = myList.iterator(); iterator.hasNext();) {
// We always have name - value pair since list length is even
SVNSkel nameElement = (SVNSkel) iterator.next();
SVNSkel valueElement = (SVNSkel) iterator.next();
String name = nameElement.getValue();
byte[] value = valueElement.isAtom() ? valueElement.getData() : null;
props.put(name, value);
}
return props;
}
public boolean isValidInheritedProperties() {
final int length = getListSize();
if (length >= 0 && (length & 1) == 0) {
for(SVNSkel child = first(); child != null; child = child.next()) {
if (!child.isAtom()) {
return false;
}
if (child.next() == null) {
return false;
}
child = child.next();
if (!child.isValidPropList()) {
return false;
}
}
}
return true;
}
public static SVNSkel createInheritedProperties(Map<String, SVNProperties> iprops) throws SVNException {
SVNSkel result = createEmptyList();
for (String path : iprops.keySet()) {
SVNSkel pathSkel = SVNSkel.createAtom(path);
SVNSkel propSkel = SVNSkel.createPropList(iprops.get(path).asMap());
result.appendChild(pathSkel);
result.appendChild(propSkel);
}
return result;
}
public List<Structure<InheritedProperties>> parseInheritedProperties() throws SVNException {
if (!isValidInheritedProperties()) {
error("iprops");
}
List<Structure<InheritedProperties>> result = new ArrayList<Structure<InheritedProperties>>();
for(SVNSkel elt = first(); elt != null; elt = elt.next()) {
final String parent = elt.getValue();
elt = elt.next();
final SVNProperties props = SVNProperties.wrap(elt.parsePropList());
final Structure<InheritedProperties> element = Structure.obtain(InheritedProperties.class);
element.set(InheritedProperties.pathOrURL, parent);
element.set(InheritedProperties.properties, props);
result.add(element);
}
return result;
}
public byte[] unparse() throws SVNException {
int approxSize = estimateUnparsedSize();
ByteBuffer buffer = ByteBuffer.allocate(approxSize);
buffer = writeTo(buffer);
buffer.flip();
byte[] raw = new byte[buffer.limit() - buffer.arrayOffset()];
System.arraycopy(buffer.array(), buffer.arrayOffset(), raw, 0, buffer.limit());
return raw;
}
public ByteBuffer writeTo(ByteBuffer buffer) throws SVNException {
if (isAtom()) {
byte[] data = getData();
if (useImplicit()) {
buffer = allocate(buffer, data.length);
buffer = buffer.put(data);
} else {
byte[] sizeBytes = getSizeBytes(data.length);
if (sizeBytes == null) {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.FS_MALFORMED_SKEL, "Unable to write size bytes to buffer");
SVNErrorManager.error(error, SVNLogType.DEFAULT);
}
buffer = allocate(buffer, sizeBytes.length + 1 + data.length);
buffer.put(sizeBytes);
try {
buffer.put(" ".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
buffer.put(" ".getBytes());
}
buffer.put(data);
}
} else {
buffer = allocate(buffer, 1);
try {
buffer.put("(".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
buffer.put("(".getBytes());
}
for (Iterator<SVNSkel> iterator = myList.iterator(); iterator.hasNext();) {
SVNSkel element = (SVNSkel) iterator.next();
buffer = element.writeTo(buffer);
if (iterator.hasNext()) {
buffer = allocate(buffer, 1);
try {
buffer.put(" ".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
buffer.put(" ".getBytes());
}
}
}
buffer = allocate(buffer, 1);
try {
buffer.put(")".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
buffer.put(")".getBytes());
}
}
return buffer;
}
private int estimateUnparsedSize() {
if (isAtom()) {
byte[] data = getData();
if (data.length < 100) {
// length bytes + whitespace
return data.length + 3;
}
return data.length + 30;
}
int total = 2;
for (Iterator<SVNSkel> iterator = myList.iterator(); iterator.hasNext();) {
SVNSkel element = (SVNSkel) iterator.next();
total += element.estimateUnparsedSize();
// space between a pair of elements
total++;
}
return total;
}
private boolean useImplicit() {
byte[] data = getData();
if (data.length == 0 || data.length >= 100) {
return false;
}
if (getType(data[0]) != TYPE_NAME) {
return false;
}
for (int i = 0; i < data.length; i++) {
byte cur = data[i];
if (getType(cur) == TYPE_SPACE || getType(cur) == TYPE_PAREN) {
return false;
}
}
return true;
}
private static ByteBuffer allocate(ByteBuffer buffer, int capacity) {
if (buffer == null) {
capacity = Math.max(capacity * 3 / 2, DEFAULT_BUFFER_SIZE);
return ByteBuffer.allocate(capacity);
}
if (capacity > buffer.remaining()) {
ByteBuffer expandedBuffer = ByteBuffer.allocate((buffer.position() + capacity) * 3 / 2);
buffer.flip();
expandedBuffer.put(buffer);
return expandedBuffer;
}
return buffer;
}
private static ByteBuffer unread(ByteBuffer buffer, int length) {
buffer.position(buffer.position() - length);
return buffer;
}
private static int parseSize(ByteBuffer buffer, int limit) {
limit = limit < 0 ? Integer.MAX_VALUE : limit;
final int maxPrefix = limit / 10;
final int maxDigit = limit % 10;
int value = 0;
int start = buffer.position();
while (buffer.hasRemaining()) {
byte cur = buffer.get();
if ('0' <= cur && cur <= '9') {
int digit = cur - '0';
if (value > maxPrefix || (value == maxPrefix && digit > maxDigit)) {
return -1;
}
value = (value * 10) + digit;
} else {
buffer = unread(buffer, 1);
break;
}
}
if (start == buffer.position()) {
return -1;
}
return value;
}
private static int writeSizeBytes(int value, byte[] data) {
int i = 0;
do {
if (i >= data.length) {
return -1;
}
data[i] = (byte) ((value % 10) + '0');
value = value / 10;
i++;
} while (value > 0);
for (int left = 0, right = i - 1; left < right; left++, right--) {
byte tmp = data[left];
data[left] = data[right];
data[right] = tmp;
}
return i;
}
private static byte[] getSizeBytes(final int value) {
int tmp = value;
int length = 0;
do {
tmp = tmp / 10;
length++;
} while (tmp > 0);
byte[] data = new byte[length];
int count = writeSizeBytes(value, data);
if (count < 0) {
return null;
}
if (count < data.length) {
byte[] result = new byte[count];
System.arraycopy(data, 0, result, 0, count);
return result;
}
return data;
}
private static void error(String type) throws SVNException {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.FS_MALFORMED_SKEL, "Malformed{0}{1} skeleton", new Object[]{type == null ? "" : " ",
type == null ? "" : type});
SVNErrorManager.error(error, SVNLogType.DEFAULT);
}
public void removeChildren(Collection<SVNSkel> childrenToRemove) {
for (SVNSkel child : childrenToRemove) {
myList.remove(child);
}
for (int i = 0; i < myList.size(); i++) {
final SVNSkel skel = myList.get(i);
skel.myNext = i < myList.size() - 1 ? myList.get(i + 1) : null;
}
}
public void removeAllChildren() {
myList.clear();
}
}