001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.wc;
013:
014: import java.io.UnsupportedEncodingException;
015: import java.nio.ByteBuffer;
016: import java.util.Arrays;
017: import java.util.Map;
018:
019: /**
020: * @version 1.1.1
021: * @author TMate Software Ltd.
022: */
023: public class SVNSubstitutor {
024:
025: private static final byte[] ALL = new byte[] { '$', '\r', '\n' };
026: private static final byte[] EOLS = new byte[] { '\r', '\n' };
027: private static final byte[] KEYWORDS = new byte[] { '$' };
028: private static final int KEYWORD_MAX_LENGTH = 255;
029:
030: private boolean myIsRepair;
031: private boolean myIsExpand;
032: private Map myKeywords;
033:
034: private byte[] myEOL;
035: private byte[] myLastEOL;
036: private byte[] myInteresting;
037: private byte[] myEOLBuffer;
038: private byte[] myKeywordBuffer;
039:
040: private int[] myLastEOLLength = new int[] { 0 };
041: private int myKeywordBufferLength;
042: private int myEOLBufferLength;
043: private int myNextSignOff;
044:
045: public SVNSubstitutor(byte[] eol, boolean repair, Map keywords,
046: boolean expand) {
047: myEOL = eol;
048: myKeywords = keywords;
049: myIsExpand = expand;
050: myIsRepair = repair;
051: myInteresting = eol != null && keywords != null ? ALL
052: : (eol != null ? EOLS : KEYWORDS);
053:
054: myEOLBuffer = new byte[2];
055: myLastEOL = new byte[2];
056: myKeywordBuffer = new byte[KEYWORD_MAX_LENGTH];
057:
058: myEOLBufferLength = 0;
059: myKeywordBufferLength = 0;
060: }
061:
062: public ByteBuffer translateChunk(ByteBuffer src, ByteBuffer dst) {
063: if (src != null) {
064: while (src.hasRemaining()) {
065: byte p = src.get(src.position());
066: if (myEOLBufferLength > 0) {
067: if (p == '\n') {
068: myEOLBuffer[myEOLBufferLength++] = src.get();
069: }
070: dst = substituteEOL(dst, myEOL, myEOL.length,
071: myLastEOL, myLastEOLLength, myEOLBuffer,
072: myEOLBufferLength, myIsRepair);
073: myEOLBufferLength = 0;
074: } else if (myKeywordBufferLength > 0 && p == '$') {
075: myKeywordBuffer[myKeywordBufferLength++] = src
076: .get();
077: byte[] keywordName = matchKeyword(myKeywordBuffer,
078: 0, myKeywordBufferLength);
079: if (keywordName == null) {
080: myKeywordBufferLength--;
081: unread(src, 1);
082: }
083: int newLength = -1;
084: if (keywordName == null
085: || (newLength = translateKeyword(
086: myKeywordBuffer, 0,
087: myKeywordBufferLength, keywordName)) >= 0
088: || myKeywordBufferLength >= KEYWORD_MAX_LENGTH) {
089: if (newLength >= 0) {
090: myKeywordBufferLength = newLength;
091: }
092: dst = write(dst, myKeywordBuffer, 0,
093: myKeywordBufferLength);
094: myNextSignOff = 0;
095: myKeywordBufferLength = 0;
096: } else {
097: if (myNextSignOff == 0) {
098: myNextSignOff = myKeywordBufferLength - 1;
099: }
100: continue;
101: }
102: } else if (myKeywordBufferLength == KEYWORD_MAX_LENGTH - 1
103: || (myKeywordBufferLength > 0 && (p == '\r' || p == '\n'))) {
104: if (myNextSignOff > 0) {
105: unread(src, myKeywordBufferLength
106: - myNextSignOff);
107: myKeywordBufferLength = myNextSignOff;
108: myNextSignOff = 0;
109: }
110: dst = write(dst, myKeywordBuffer, 0,
111: myKeywordBufferLength);
112: myKeywordBufferLength = 0;
113: } else if (myKeywordBufferLength > 0) {
114: myKeywordBuffer[myKeywordBufferLength++] = src
115: .get();
116: continue;
117: }
118: int len = 0;
119: while (src.position() + len < src.limit()
120: && !isInteresting(src.get(src.position() + len))) {
121: len++;
122: }
123: if (len > 0) {
124: dst = write(dst, src.array(), src.arrayOffset()
125: + src.position(), len);
126: }
127: src.position(src.position() + len);
128: if (src.hasRemaining()) {
129: // setup interesting.
130: p = src.get();
131: switch (p) {
132: case '$':
133: myKeywordBuffer[myKeywordBufferLength++] = p;
134: break;
135: case '\r':
136: myEOLBuffer[myEOLBufferLength++] = p;
137: break;
138: case '\n':
139: myEOLBuffer[myEOLBufferLength++] = p;
140: dst = substituteEOL(dst, myEOL, myEOL.length,
141: myLastEOL, myLastEOLLength,
142: myEOLBuffer, myEOLBufferLength,
143: myIsRepair);
144: myEOLBufferLength = 0;
145: break;
146: }
147: }
148: }
149: } else {
150: // flush buffers if any.
151: if (myEOLBufferLength > 0) {
152: dst = substituteEOL(dst, myEOL, myEOL.length,
153: myLastEOL, myLastEOLLength, myEOLBuffer,
154: myEOLBufferLength, myIsRepair);
155: myEOLBufferLength = 0;
156: }
157: if (myKeywordBufferLength > 0) {
158: dst = write(dst, myKeywordBuffer, 0,
159: myKeywordBufferLength);
160: myKeywordBufferLength = 0;
161: }
162: }
163: return dst;
164: }
165:
166: private boolean isInteresting(byte p) {
167: for (int i = 0; i < myInteresting.length; i++) {
168: if (p == myInteresting[i]) {
169: return true;
170: }
171: }
172: return false;
173: }
174:
175: private byte[] matchKeyword(byte[] src, int offset, int length) {
176: if (myKeywords == null) {
177: return null;
178: }
179: String name = null;
180: int len = 0;
181: try {
182: for (int i = 0; i < length - 2
183: && src[offset + i + 1] != ':'; i++) {
184: len++;
185: }
186: if (len == 0) {
187: return null;
188: }
189: name = new String(src, offset + 1, len, "ASCII");
190: } catch (UnsupportedEncodingException e) {
191: //
192: }
193: if (name != null && myKeywords.containsKey(name)) {
194: byte[] nameBytes = new byte[len];
195: System.arraycopy(src, offset + 1, nameBytes, 0, len);
196: return nameBytes;
197: }
198: return null;
199: }
200:
201: private int translateKeyword(byte[] src, int offset, int length,
202: byte[] name) {
203: if (myKeywords == null) {
204: return -1;
205: }
206: String nameStr;
207: try {
208: nameStr = new String(name, "ASCII");
209: } catch (UnsupportedEncodingException e) {
210: return -1;
211: }
212: byte[] value = (byte[]) myKeywords.get(nameStr);
213: if (myKeywords.containsKey(nameStr)) {
214: if (!myIsExpand) {
215: value = null;
216: }
217: return substituteKeyword(src, offset, length, name, value);
218: }
219: return -1;
220: }
221:
222: private static void unread(ByteBuffer buffer, int length) {
223: buffer.position(buffer.position() - length);
224: }
225:
226: private static int substituteKeyword(byte[] src, int offset,
227: int length, byte[] keyword, byte[] value) {
228: int pointer;
229: if (length < keyword.length + 2) {
230: return -1;
231: }
232: for (int i = 0; i < keyword.length; i++) {
233: if (keyword[i] != src[offset + 1 + i]) {
234: return -1;
235: }
236: }
237: pointer = offset + 1 + keyword.length;
238: if (src[pointer] == ':'
239: && src[pointer + 1] == ':'
240: && src[pointer + 2] == ' '
241: && (src[offset + length - 2] == ' ' || src[offset
242: + length - 2] == '#')
243: && 6 + keyword.length < length) {
244: // fixed size keyword.
245: if (value == null) {
246: pointer += 2;
247: while (src[pointer] != '$') {
248: src[pointer++] = ' ';
249: }
250: } else {
251: int maxValueLength = length - (6 + keyword.length);
252: if (value.length <= maxValueLength) {
253: // put value, then spaces.
254: System.arraycopy(value, 0, src, pointer + 3,
255: value.length);
256: pointer += 3 + value.length;
257: while (src[pointer] != '$') {
258: src[pointer++] = ' ';
259: }
260: } else {
261: System.arraycopy(value, 0, src, pointer + 3,
262: maxValueLength);
263: src[offset + length - 2] = '#';
264: src[offset + length - 1] = '$';
265: }
266: }
267: return length;
268: } else if (src[pointer] == '$'
269: || (src[pointer] == ':' && src[pointer + 1] == '$')) {
270: if (value != null) {
271: src[pointer] = ':';
272: src[pointer + 1] = ' ';
273: if (value.length > 0) {
274: int valueLength = value.length;
275: if (valueLength > KEYWORD_MAX_LENGTH - 5
276: - keyword.length) {
277: valueLength = KEYWORD_MAX_LENGTH - 5
278: - keyword.length;
279: }
280: System.arraycopy(value, 0, src, pointer + 2,
281: valueLength);
282: src[pointer + 2 + valueLength] = ' ';
283: src[pointer + 3 + valueLength] = '$';
284: length = 5 + keyword.length + valueLength;
285: } else {
286: src[pointer + 2] = '$';
287: length = 4 + keyword.length;
288: }
289: }
290: return length;
291: } else if (length >= keyword.length + 4 && src[pointer] == ':'
292: && src[pointer + 1] == ' '
293: && src[offset + length - 2] == ' ') {
294: if (value == null) {
295: src[pointer] = '$';
296: length = 2 + keyword.length;
297: } else {
298: src[pointer] = ':';
299: src[pointer + 1] = ' ';
300: if (value.length > 0) {
301: int valueLength = value.length;
302: if (valueLength > KEYWORD_MAX_LENGTH - 5
303: - keyword.length) {
304: valueLength = KEYWORD_MAX_LENGTH - 5
305: - keyword.length;
306: }
307: System.arraycopy(value, 0, src, pointer + 2,
308: valueLength);
309: src[pointer + 2 + valueLength] = ' ';
310: src[pointer + 3 + valueLength] = '$';
311: length = 5 + keyword.length + valueLength;
312: } else {
313: src[pointer + 2] = '$';
314: length = 4 + keyword.length;
315: }
316: }
317: return length;
318: }
319: return -1;
320: }
321:
322: private static ByteBuffer substituteEOL(ByteBuffer dst, byte[] eol,
323: int eolLength, byte[] lastEOL, int[] lastEOLLength,
324: byte[] nextEOL, int nextEOLLength, boolean repair) {
325: if (lastEOLLength[0] > 0) {
326: if (!repair
327: && (lastEOLLength[0] != nextEOLLength || !Arrays
328: .equals(lastEOL, nextEOL))) {
329: // inconsistent EOLs.
330: }
331: } else {
332: lastEOLLength[0] = nextEOLLength;
333: lastEOL[0] = nextEOL[0];
334: lastEOL[1] = nextEOL[1];
335: }
336: return write(dst, eol, 0, eolLength);
337: }
338:
339: private static ByteBuffer write(ByteBuffer dst, byte[] bytes,
340: int offset, int length) {
341: if (dst.remaining() < length) {
342: ByteBuffer newDst = ByteBuffer
343: .allocate((dst.position() + length) * 3 / 2);
344: dst.flip();
345: dst = newDst.put(dst);
346: }
347: return dst.put(bytes, offset, length);
348: }
349: }
|