/*
* ====================================================================
* 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.wc;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNSubstitutor {
private static final byte[] ALL = new byte[] {'$', '\r', '\n'};
private static final byte[] EOLS = new byte[] {'\r', '\n'};
private static final byte[] KEYWORDS = new byte[] {'$'};
private static final int KEYWORD_MAX_LENGTH = 255;
private boolean myIsRepair;
private boolean myIsExpand;
private Map myKeywords;
private byte[] myEOL;
private byte[] myLastEOL;
private byte[] myInteresting;
private byte[] myEOLBuffer;
private byte[] myKeywordBuffer;
private int[] myLastEOLLength = new int[] {0};
private int myKeywordBufferLength;
private int myEOLBufferLength;
public SVNSubstitutor(byte[] eol, boolean repair, Map keywords, boolean expand) {
myEOL = eol;
myKeywords = keywords;
myIsExpand = expand;
myIsRepair = repair;
myInteresting = eol != null && keywords != null ? ALL : (eol != null ? EOLS : KEYWORDS);
myEOLBuffer = new byte[2];
myLastEOL = new byte[2];
myKeywordBuffer = new byte[KEYWORD_MAX_LENGTH];
myEOLBufferLength = 0;
myKeywordBufferLength = 0;
}
public ByteBuffer translateChunk(ByteBuffer src, ByteBuffer dst) throws SVNException {
if (src != null) {
int nextSignOff = 0;
while(src.hasRemaining()) {
byte p = src.get(src.position());
if (myEOLBufferLength > 0) {
if (p == '\n') {
myEOLBuffer[myEOLBufferLength++] = src.get();
}
dst = substituteEOL(dst, myEOL, myEOL.length,
myLastEOL, myLastEOLLength,
myEOLBuffer, myEOLBufferLength, myIsRepair);
myEOLBufferLength = 0;
} else if (myKeywordBufferLength > 0 && p == '$') {
myKeywordBuffer[myKeywordBufferLength++] = src.get();
byte[] keywordName = matchKeyword(myKeywordBuffer, 0, myKeywordBufferLength);
if (keywordName == null) {
myKeywordBufferLength--;
unread(src, 1);
}
int newLength = -1;
if (keywordName == null || (newLength = translateKeyword(myKeywordBuffer, 0, myKeywordBufferLength, keywordName)) >= 0 || myKeywordBufferLength >= KEYWORD_MAX_LENGTH) {
if (newLength >= 0) {
myKeywordBufferLength = newLength;
}
dst = write(dst, myKeywordBuffer, 0, myKeywordBufferLength);
nextSignOff = 0;
myKeywordBufferLength = 0;
} else {
if (nextSignOff == 0) {
nextSignOff = myKeywordBufferLength - 1;
}
continue;
}
} else if (myKeywordBufferLength == KEYWORD_MAX_LENGTH - 1 || (myKeywordBufferLength > 0 && (p == '\r' || p == '\n'))) {
if (nextSignOff > 0) {
unread(src, myKeywordBufferLength - nextSignOff);
myKeywordBufferLength = nextSignOff;
nextSignOff = 0;
}
dst = write(dst, myKeywordBuffer, 0, myKeywordBufferLength);
myKeywordBufferLength = 0;
} else if (myKeywordBufferLength > 0) {
myKeywordBuffer[myKeywordBufferLength++] = src.get();
continue;
}
int len = 0;
while(src.position() + len < src.limit() && !isInteresting(src.get(src.position() + len))) {
len++;
}
if (len > 0) {
dst = write(dst, src.array(), src.arrayOffset() + src.position(), len);
}
src.position(src.position() + len);
if (src.hasRemaining()) {
// setup interesting.
p = src.get();
switch (p) {
case '$':
myKeywordBuffer[myKeywordBufferLength++] = p;
break;
case '\r':
myEOLBuffer[myEOLBufferLength++] = p;
break;
case '\n':
myEOLBuffer[myEOLBufferLength++] = p;
dst = substituteEOL(dst,
myEOL, myEOL.length,
myLastEOL, myLastEOLLength,
myEOLBuffer, myEOLBufferLength, myIsRepair);
myEOLBufferLength = 0;
break;
}
}
}
} else {
// flush buffers if any.
if (myEOLBufferLength > 0) {
dst = substituteEOL(dst,
myEOL, myEOL.length,
myLastEOL, myLastEOLLength,
myEOLBuffer, myEOLBufferLength, myIsRepair);
myEOLBufferLength = 0;
}
if (myKeywordBufferLength > 0) {
dst = write(dst, myKeywordBuffer, 0, myKeywordBufferLength);
myKeywordBufferLength = 0;
}
}
return dst;
}
private boolean isInteresting(byte p) {
for(int i = 0; i < myInteresting.length; i++) {
if (p == myInteresting[i]) {
return true;
}
}
return false;
}
private byte[] matchKeyword(byte[] src, int offset, int length) {
if (myKeywords == null) {
return null;
}
String name = null;
int len = 0;
try {
for(int i = 0; i < length - 2 && src[offset + i + 1] != ':'; i++) {
len++;
}
if (len == 0) {
return null;
}
name = new String(src, offset + 1, len, "ASCII");
} catch (UnsupportedEncodingException e) {
//
}
if (name != null && myKeywords.containsKey(name)) {
byte[] nameBytes = new byte[len];
System.arraycopy(src, offset + 1, nameBytes, 0, len);
return nameBytes;
}
return null;
}
private int translateKeyword(byte[] src, int offset, int length, byte[] name) {
if (myKeywords == null) {
return -1;
}
String nameStr;
try {
nameStr = new String(name, "ASCII");
} catch (UnsupportedEncodingException e) {
return -1;
}
byte[] value = (byte[]) myKeywords.get(nameStr);
if (myKeywords.containsKey(nameStr)) {
if (!myIsExpand) {
value = null;
}
return substituteKeyword(src, offset, length, name, value);
}
return -1;
}
private static void unread(ByteBuffer buffer, int length) {
buffer.position(buffer.position() - length);
}
private static int substituteKeyword(byte[] src, int offset, int length, byte[] keyword, byte[] value) {
int pointer;
if (length < keyword.length + 2) {
return -1;
}
for(int i = 0; i < keyword.length; i++) {
if (keyword[i] != src[offset + 1 + i]) {
return -1;
}
}
pointer = offset + 1 + keyword.length;
if (src[pointer] == ':' && src[pointer + 1] == ':' && src[pointer + 2] == ' ' &&
(src[offset + length - 2] == ' ' || src[offset + length - 2] == '#') &&
6 + keyword.length < length) {
// fixed size keyword.
if (value == null) {
pointer += 2;
while(src[pointer] != '$') {
src[pointer++] = ' ';
}
} else {
int maxValueLength = length - (6 + keyword.length);
if (value.length <= maxValueLength) {
// put value, then spaces.
System.arraycopy(value, 0, src, pointer + 3, value.length);
pointer += 3 + value.length;
while(src[pointer] != '$') {
src[pointer++] = ' ';
}
} else {
System.arraycopy(value, 0, src, pointer + 3, maxValueLength);
src[offset + length - 2] = '#';
src[offset + length - 1] = '$';
}
}
return length;
} else if (src[pointer] == '$' || (src[pointer] == ':' && src[pointer + 1] == '$')) {
if (value != null) {
src[pointer] = ':';
src[pointer + 1] = ' ';
if (value.length > 0) {
int valueLength = value.length;
if (valueLength > KEYWORD_MAX_LENGTH - 5 - keyword.length) {
valueLength = KEYWORD_MAX_LENGTH - 5 - keyword.length;
}
System.arraycopy(value, 0, src, pointer + 2, valueLength);
src[pointer + 2 + valueLength] = ' ';
src[pointer + 3 + valueLength] = '$';
length = 5 + keyword.length + valueLength;
} else {
src[pointer + 2] = '$';
length = 4 + keyword.length;
}
}
return length;
} else if (length >= keyword.length + 4 && src[pointer] == ':' && src[pointer + 1] == ' ' && src[offset + length - 2] == ' ') {
if (value == null) {
src[pointer] = '$';
length = 2 + keyword.length;
} else {
src[pointer] = ':';
src[pointer + 1] = ' ';
if (value.length > 0) {
int valueLength = value.length;
if (valueLength > KEYWORD_MAX_LENGTH - 5 - keyword.length) {
valueLength = KEYWORD_MAX_LENGTH - 5 - keyword.length;
}
System.arraycopy(value, 0, src, pointer + 2, valueLength);
src[pointer + 2 + valueLength] = ' ';
src[pointer + 3 + valueLength] = '$';
length = 5 + keyword.length + valueLength;
} else {
src[pointer + 2] = '$';
length = 4 + keyword.length;
}
}
return length;
}
return -1;
}
private static ByteBuffer substituteEOL(ByteBuffer dst,
byte[] eol, int eolLength, byte[] lastEOL, int[] lastEOLLength, byte[] nextEOL, int nextEOLLength,
boolean repair) throws SVNException {
if (lastEOLLength[0] > 0) {
if (!repair && (lastEOLLength[0] != nextEOLLength || !Arrays.equals(lastEOL, nextEOL))) {
// inconsistent EOLs.
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_INCONSISTENT_EOL);
SVNErrorManager.error(err, SVNLogType.DEFAULT);
}
} else {
lastEOLLength[0] = nextEOLLength;
lastEOL[0] = nextEOL[0];
lastEOL[1] = nextEOL[1];
}
return write(dst, eol, 0, eolLength);
}
private static ByteBuffer write(ByteBuffer dst, byte[] bytes, int offset, int length) {
if (dst.remaining() < length) {
ByteBuffer newDst = ByteBuffer.allocate((dst.position() + length)*3/2);
dst.flip();
dst = newDst.put(dst);
}
return dst.put(bytes, offset, length);
}
}