/* * ==================================================================== * 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); } }