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.io.fs;
013:
014: import java.io.ByteArrayOutputStream;
015: import java.io.File;
016: import java.io.FileInputStream;
017: import java.io.IOException;
018: import java.nio.ByteBuffer;
019: import java.nio.channels.FileChannel;
020: import java.nio.charset.Charset;
021: import java.nio.charset.CharsetDecoder;
022: import java.nio.charset.MalformedInputException;
023: import java.security.MessageDigest;
024: import java.security.NoSuchAlgorithmException;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import org.tmatesoft.svn.core.SVNErrorCode;
029: import org.tmatesoft.svn.core.SVNErrorMessage;
030: import org.tmatesoft.svn.core.SVNException;
031: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
032: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
033:
034: /**
035: * @version 1.1.1
036: * @author TMate Software Ltd.
037: */
038: public class FSFile {
039:
040: private File myFile;
041: private FileChannel myChannel;
042: private FileInputStream myInputStream;
043: private long myPosition;
044:
045: private long myBufferPosition;
046:
047: private ByteBuffer myBuffer;
048: private ByteBuffer myReadLineBuffer;
049: private CharsetDecoder myDecoder;
050: private MessageDigest myDigest;
051:
052: public FSFile(File file) {
053: myFile = file;
054: myPosition = 0;
055: myBufferPosition = 0;
056: myBuffer = ByteBuffer.allocate(4096);
057: myReadLineBuffer = ByteBuffer.allocate(4096);
058: myDecoder = Charset.forName("UTF-8").newDecoder();
059: }
060:
061: public void seek(long position) {
062: myPosition = position;
063: }
064:
065: public long position() {
066: return myPosition;
067: }
068:
069: public long size() {
070: return myFile.length();
071: }
072:
073: public void resetDigest() {
074: if (myDigest == null) {
075: try {
076: myDigest = MessageDigest.getInstance("MD5");
077: } catch (NoSuchAlgorithmException e) {
078: }
079: }
080: myDigest.reset();
081: }
082:
083: public String digest() {
084: String digest = SVNFileUtil.toHexDigest(myDigest);
085: myDigest = null;
086: return digest;
087: }
088:
089: public int readInt() throws SVNException {
090: String line = readLine(80);
091: if (line == null) {
092: SVNErrorMessage err = SVNErrorMessage.create(
093: SVNErrorCode.BAD_VERSION_FILE_FORMAT,
094: "First line of ''{0}'' contains non-digit", myFile);
095: SVNErrorManager.error(err);
096: }
097: try {
098: return Integer.parseInt(line);
099: } catch (NumberFormatException nfe) {
100: SVNErrorMessage err = SVNErrorMessage.create(
101: SVNErrorCode.BAD_VERSION_FILE_FORMAT,
102: "First line of ''{0}'' contains non-digit", myFile);
103: SVNErrorManager.error(err);
104: }
105: return -1;
106: }
107:
108: public String readLine(int limit) throws SVNException {
109: allocateReadBuffer(limit);
110: try {
111: while (myReadLineBuffer.hasRemaining()) {
112: int b = read();
113: if (b < 0) {
114: SVNErrorMessage err = SVNErrorMessage.create(
115: SVNErrorCode.STREAM_UNEXPECTED_EOF,
116: "Can''t read length line from file {0}",
117: getFile());
118: SVNErrorManager.error(err);
119: } else if (b == '\n') {
120: break;
121: }
122: myReadLineBuffer.put((byte) (b & 0XFF));
123: }
124: myReadLineBuffer.flip();
125: return myDecoder.decode(myReadLineBuffer).toString();
126: } catch (IOException e) {
127: SVNErrorMessage err = SVNErrorMessage
128: .create(
129: SVNErrorCode.FS_CORRUPT,
130: "Can''t read length line from file {0}: {1}",
131: new Object[] { getFile(),
132: e.getLocalizedMessage() });
133: SVNErrorManager.error(err, e);
134: }
135: return null;
136: }
137:
138: public String readLine(StringBuffer buffer) throws SVNException {
139: if (buffer == null) {
140: buffer = new StringBuffer();
141: }
142: boolean endOfLineMet = false;
143: try {
144: while (!endOfLineMet) {
145: allocateReadBuffer(160);
146: while (myReadLineBuffer.hasRemaining()) {
147: int b = read();
148: if (b < 0) {
149: SVNErrorMessage err = SVNErrorMessage
150: .create(
151: SVNErrorCode.STREAM_UNEXPECTED_EOF,
152: "Can''t read length line from file {0}",
153: getFile());
154: SVNErrorManager.error(err);
155: } else if (b == '\n') {
156: endOfLineMet = true;
157: break;
158: }
159: myReadLineBuffer.put((byte) (b & 0XFF));
160: }
161: myReadLineBuffer.flip();
162: buffer.append(myDecoder.decode(myReadLineBuffer)
163: .toString());
164: }
165: } catch (IOException e) {
166: SVNErrorMessage err = SVNErrorMessage
167: .create(
168: SVNErrorCode.FS_CORRUPT,
169: "Can''t read length line from file {0}: {1}",
170: new Object[] { getFile(),
171: e.getLocalizedMessage() });
172: SVNErrorManager.error(err, e);
173: }
174: return buffer.toString();
175: }
176:
177: public Map readProperties(boolean allowEOF) throws SVNException {
178: Map map = new HashMap();
179: String line = null;
180: try {
181: while (true) {
182: try {
183: line = readLine(160); // K length or END, there may be EOF.
184: } catch (SVNException e) {
185: if (allowEOF
186: && e.getErrorMessage().getErrorCode() == SVNErrorCode.STREAM_UNEXPECTED_EOF) {
187: break;
188: }
189: SVNErrorMessage err = SVNErrorMessage
190: .create(SVNErrorCode.MALFORMED_FILE);
191: SVNErrorManager.error(err, e);
192: }
193: if (line == null || "".equals(line)) {
194: break;
195: } else if (!allowEOF && "END".equals(line)) {
196: break;
197: }
198: char kind = line.charAt(0);
199: int length = -1;
200: if ((kind != 'K' && kind != 'D') || line.length() < 3
201: || line.charAt(1) != ' ' || line.length() < 3) {
202: SVNErrorMessage err = SVNErrorMessage
203: .create(SVNErrorCode.MALFORMED_FILE);
204: SVNErrorManager.error(err);
205: }
206: try {
207: length = Integer.parseInt(line.substring(2));
208: } catch (NumberFormatException nfe) {
209: SVNErrorMessage err = SVNErrorMessage
210: .create(SVNErrorCode.MALFORMED_FILE);
211: SVNErrorManager.error(err);
212: }
213: if (length < 0) {
214: SVNErrorMessage err = SVNErrorMessage
215: .create(SVNErrorCode.MALFORMED_FILE);
216: SVNErrorManager.error(err);
217: }
218: allocateReadBuffer(length + 1);
219: read(myReadLineBuffer);
220: myReadLineBuffer.flip();
221: myReadLineBuffer.limit(myReadLineBuffer.limit() - 1);
222: int pos = myReadLineBuffer.position();
223: int limit = myReadLineBuffer.limit();
224: String key = null;
225: try {
226: key = myDecoder.decode(myReadLineBuffer).toString();
227: } catch (MalformedInputException mfi) {
228: key = new String(myReadLineBuffer.array(),
229: myReadLineBuffer.arrayOffset() + pos, limit
230: - pos);
231: }
232: if (kind == 'D') {
233: map.put(key, null);
234: continue;
235: }
236: line = readLine(160);
237: if (line == null || line.length() < 3
238: || line.charAt(0) != 'V'
239: || line.charAt(1) != ' ') {
240: SVNErrorMessage err = SVNErrorMessage
241: .create(SVNErrorCode.MALFORMED_FILE);
242: SVNErrorManager.error(err);
243: }
244: try {
245: length = Integer.parseInt(line.substring(2));
246: } catch (NumberFormatException nfe) {
247: SVNErrorMessage err = SVNErrorMessage
248: .create(SVNErrorCode.MALFORMED_FILE);
249: SVNErrorManager.error(err);
250: }
251: if (length < 0) {
252: SVNErrorMessage err = SVNErrorMessage
253: .create(SVNErrorCode.MALFORMED_FILE);
254: SVNErrorManager.error(err);
255: }
256: allocateReadBuffer(length + 1);
257: read(myReadLineBuffer);
258: myReadLineBuffer.flip();
259: myReadLineBuffer.limit(myReadLineBuffer.limit() - 1);
260: String value = null;
261: pos = myReadLineBuffer.position();
262: limit = myReadLineBuffer.limit();
263: try {
264: value = myDecoder.decode(myReadLineBuffer)
265: .toString();
266: } catch (MalformedInputException mfi) {
267: value = new String(myReadLineBuffer.array(),
268: myReadLineBuffer.arrayOffset() + pos, limit
269: - pos);
270: }
271: map.put(key, value);
272: }
273: } catch (IOException e) {
274: SVNErrorMessage err = SVNErrorMessage
275: .create(SVNErrorCode.MALFORMED_FILE);
276: SVNErrorManager.error(err, e);
277: }
278: return map;
279: }
280:
281: public Map readHeader() throws SVNException {
282: Map map = new HashMap();
283: String line;
284: while (true) {
285: line = readLine(1024);
286: if ("".equals(line)) {
287: break;
288: }
289: if (line == null) {
290:
291: }
292: int colonIndex = line.indexOf(':');
293: if (colonIndex <= 0 || line.length() <= colonIndex + 2) {
294: SVNErrorMessage err = SVNErrorMessage.create(
295: SVNErrorCode.FS_CORRUPT,
296: "Found malformed header in revision file");
297: SVNErrorManager.error(err);
298: } else if (line.charAt(colonIndex + 1) != ' ') {
299: SVNErrorMessage err = SVNErrorMessage.create(
300: SVNErrorCode.FS_CORRUPT,
301: "Found malformed header in revision file");
302: SVNErrorManager.error(err);
303: }
304: String key = line.substring(0, colonIndex);
305: String value = line.substring(colonIndex + 2);
306: map.put(key, value);
307: }
308: return map;
309: }
310:
311: public int read() throws IOException {
312: if (myChannel == null || myPosition < myBufferPosition
313: || myPosition >= myBufferPosition + myBuffer.limit()) {
314: if (fill() <= 0) {
315: return -1;
316: }
317: } else {
318: myBuffer.position((int) (myPosition - myBufferPosition));
319: }
320: int r = (myBuffer.get() & 0xFF);
321: if (myDigest != null) {
322: myDigest.update((byte) r);
323: }
324: myPosition++;
325: return r;
326: }
327:
328: public int read(ByteBuffer target) throws IOException {
329: int read = 0;
330: while (target.hasRemaining()) {
331: if (fill() < 0) {
332: return read > 0 ? read : -1;
333: }
334: myBuffer.position((int) (myPosition - myBufferPosition));
335:
336: int couldRead = Math.min(myBuffer.remaining(), target
337: .remaining());
338: int readFrom = myBuffer.position() + myBuffer.arrayOffset();
339: target.put(myBuffer.array(), readFrom, couldRead);
340: if (myDigest != null) {
341: myDigest.update(myBuffer.array(), readFrom, couldRead);
342: }
343: myPosition += couldRead;
344: read += couldRead;
345: myBuffer.position(myBuffer.position() + couldRead);
346: }
347: return read;
348: }
349:
350: public int read(byte[] buffer, int offset, int length)
351: throws IOException {
352: int read = 0;
353: int toRead = length;
354: while (toRead > 0) {
355: if (fill() < 0) {
356: return read > 0 ? read : -1;
357: }
358: myBuffer.position((int) (myPosition - myBufferPosition));
359:
360: int couldRead = Math.min(myBuffer.remaining(), toRead);
361: myBuffer.get(buffer, offset, couldRead);
362: if (myDigest != null) {
363: myDigest.update(buffer, offset, couldRead);
364: }
365: toRead -= couldRead;
366: offset += couldRead;
367: myPosition += couldRead;
368: read += couldRead;
369: }
370: return read;
371: }
372:
373: public File getFile() {
374: return myFile;
375: }
376:
377: public void close() {
378: if (myChannel != null) {
379: try {
380: myChannel.close();
381: } catch (IOException e) {
382: }
383: SVNFileUtil.closeFile(myInputStream);
384: myChannel = null;
385: myInputStream = null;
386: myPosition = 0;
387: myDigest = null;
388: }
389:
390: }
391:
392: private int fill() throws IOException {
393: if (myChannel == null || myPosition < myBufferPosition
394: || myPosition >= myBufferPosition + myBuffer.limit()) {
395: myBufferPosition = myPosition;
396: getChannel().position(myBufferPosition);
397: myBuffer.clear();
398: int read = getChannel().read(myBuffer);
399: myBuffer.position(0);
400: myBuffer.limit(read >= 0 ? read : 0);
401: return read;
402: }
403: return 0;
404: }
405:
406: private void allocateReadBuffer(int limit) {
407: if (limit > myReadLineBuffer.capacity()) {
408: myReadLineBuffer = ByteBuffer.allocate(limit * 3 / 2);
409: }
410: myReadLineBuffer.clear();
411: myReadLineBuffer.limit(limit);
412: }
413:
414: private FileChannel getChannel() throws IOException {
415: if (myChannel == null) {
416: myInputStream = new FileInputStream(myFile);
417: myChannel = myInputStream.getChannel();
418: }
419: return myChannel;
420: }
421:
422: public PathInfo readPathInfoFromReportFile() throws IOException {
423: int firstByte = read();
424: if (firstByte == -1 || firstByte == '-') {
425: return null;
426: }
427: String path = readStringFromReportFile();
428: String linkPath = read() == '+' ? readStringFromReportFile()
429: : null;
430: long revision = readRevisionFromReportFile();
431: boolean startEmpty = read() == '+' ? true : false;
432: String lockToken = read() == '+' ? readStringFromReportFile()
433: : null;
434: return new PathInfo(path, linkPath, lockToken, revision,
435: startEmpty);
436: }
437:
438: private String readStringFromReportFile() throws IOException {
439: int length = readNumberFromReportFile();
440: if (length == 0) {
441: return "";
442: }
443: byte[] buffer = new byte[length];
444: read(buffer, 0, length);
445: return new String(buffer, "UTF-8");
446: }
447:
448: private int readNumberFromReportFile() throws IOException {
449: int b;
450: ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
451: while ((b = read()) != ':') {
452: resultStream.write(b);
453: }
454: return Integer.parseInt(new String(resultStream.toByteArray(),
455: "UTF-8"), 10);
456: }
457:
458: private long readRevisionFromReportFile() throws IOException {
459: if (read() == '+') {
460: return readNumberFromReportFile();
461: }
462: return FSRepository.SVN_INVALID_REVNUM;
463: }
464:
465: }
|