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.util.Collections;
015: import java.util.Map;
016:
017: import org.tmatesoft.svn.core.SVNErrorCode;
018: import org.tmatesoft.svn.core.SVNErrorMessage;
019: import org.tmatesoft.svn.core.SVNException;
020: import org.tmatesoft.svn.core.SVNNodeKind;
021: import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
022: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
023: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
024:
025: /**
026: * @version 1.1.1
027: * @author TMate Software Ltd.
028: */
029: public class FSRevisionNode {
030:
031: // rev-node files keywords
032: public static final String HEADER_ID = "id";
033: public static final String HEADER_TYPE = "type";
034: public static final String HEADER_COUNT = "count";
035: public static final String HEADER_PROPS = "props";
036: public static final String HEADER_TEXT = "text";
037: public static final String HEADER_CPATH = "cpath";
038: public static final String HEADER_PRED = "pred";
039: public static final String HEADER_COPYFROM = "copyfrom";
040: public static final String HEADER_COPYROOT = "copyroot";
041: public static final String HEADER_IS_FRESH_TXN_ROOT = "is-fresh-txn-root";
042:
043: // id: a.b.r<revID>/offset
044: private FSID myId;
045:
046: // type: 'dir' or 'file'
047: private SVNNodeKind myType;
048:
049: // count: count of revs since base
050: private long myCount;
051:
052: // (_)a.(_)b.tx-y
053:
054: // pred: a.b.r<revID>/offset
055: private FSID myPredecessorId;
056:
057: // text: <rev> <offset> <length> <size> <digest>
058: private FSRepresentation myTextRepresentation;
059:
060: // props: <rev> <offset> <length> <size> <digest>
061: private FSRepresentation myPropsRepresentation;
062:
063: // cpath: <path>
064: private String myCreatedPath;
065:
066: // copyfrom: <revID> <path>
067: private long myCopyFromRevision;
068: private String myCopyFromPath;
069:
070: // copyroot: <revID> <created-path>
071: private long myCopyRootRevision;
072: private String myCopyRootPath;
073:
074: // for only node-revs representing dirs
075: private Map myDirContents;
076:
077: //in case of txn root: whether it wasn't
078: //changed yet (fresh) or was
079: private boolean myIsFreshTxnRoot;
080: private FSID myFreshRootPredecessorId;
081:
082: public FSRevisionNode() {
083: }
084:
085: public void setId(FSID revNodeID) {
086: myId = revNodeID;
087: }
088:
089: public void setType(SVNNodeKind nodeKind) {
090: myType = nodeKind;
091: }
092:
093: public void setCount(long count) {
094: myCount = count;
095: }
096:
097: public void setPredecessorId(FSID predRevNodeId) {
098: myPredecessorId = predRevNodeId;
099: }
100:
101: public void setTextRepresentation(FSRepresentation textRepr) {
102: myTextRepresentation = textRepr;
103: }
104:
105: public void setPropsRepresentation(FSRepresentation propsRepr) {
106: myPropsRepresentation = propsRepr;
107: }
108:
109: public void setCreatedPath(String cpath) {
110: myCreatedPath = cpath;
111: }
112:
113: public void setCopyFromRevision(long copyFromRev) {
114: myCopyFromRevision = copyFromRev;
115: }
116:
117: public void setCopyFromPath(String copyFromPath) {
118: myCopyFromPath = copyFromPath;
119: }
120:
121: public void setCopyRootRevision(long copyRootRev) {
122: myCopyRootRevision = copyRootRev;
123: }
124:
125: public void setCopyRootPath(String copyRootPath) {
126: myCopyRootPath = copyRootPath;
127: }
128:
129: public FSID getId() {
130: return myId;
131: }
132:
133: public SVNNodeKind getType() {
134: return myType;
135: }
136:
137: public long getCount() {
138: return myCount;
139: }
140:
141: public FSID getPredecessorId() {
142: return myPredecessorId;
143: }
144:
145: // text
146: public FSRepresentation getTextRepresentation() {
147: return myTextRepresentation;
148: }
149:
150: // props
151: public FSRepresentation getPropsRepresentation() {
152: return myPropsRepresentation;
153: }
154:
155: public String getCreatedPath() {
156: return myCreatedPath;
157: }
158:
159: public long getCreatedRevision() {
160: if (myFreshRootPredecessorId != null) {
161: return myFreshRootPredecessorId.getRevision();
162: }
163: return myId.getRevision();
164: }
165:
166: public long getCopyFromRevision() {
167: return myCopyFromRevision;
168: }
169:
170: public String getCopyFromPath() {
171: return myCopyFromPath;
172: }
173:
174: public long getCopyRootRevision() {
175: return myCopyRootRevision;
176: }
177:
178: public String getCopyRootPath() {
179: return myCopyRootPath;
180: }
181:
182: public static FSRevisionNode dumpRevisionNode(FSRevisionNode revNode) {
183: FSRevisionNode clone = new FSRevisionNode();
184: clone.setId(revNode.getId());
185: if (revNode.getPredecessorId() != null) {
186: clone.setPredecessorId(revNode.getPredecessorId());
187: }
188: clone.setType(revNode.getType());
189: clone.setCopyFromPath(revNode.getCopyFromPath());
190: clone.setCopyFromRevision(revNode.getCopyFromRevision());
191: clone.setCopyRootPath(revNode.getCopyRootPath());
192: clone.setCopyRootRevision(revNode.getCopyRootRevision());
193: clone.setCount(revNode.getCount());
194: clone.setCreatedPath(revNode.getCreatedPath());
195: if (revNode.getPropsRepresentation() != null) {
196: clone.setPropsRepresentation(new FSRepresentation(revNode
197: .getPropsRepresentation()));
198: }
199: if (revNode.getTextRepresentation() != null) {
200: clone.setTextRepresentation(new FSRepresentation(revNode
201: .getTextRepresentation()));
202: }
203: return clone;
204: }
205:
206: protected Map getDirContents() {
207: return myDirContents;
208: }
209:
210: public void setDirContents(Map dirContents) {
211: myDirContents = dirContents;
212: }
213:
214: public static FSRevisionNode fromMap(Map headers)
215: throws SVNException {
216: FSRevisionNode revNode = new FSRevisionNode();
217:
218: // Read the rev-node id.
219: String revNodeId = (String) headers
220: .get(FSRevisionNode.HEADER_ID);
221: if (revNodeId == null) {
222: SVNErrorMessage err = SVNErrorMessage.create(
223: SVNErrorCode.FS_CORRUPT,
224: "Missing node-id in node-rev");
225: SVNErrorManager.error(err);
226: }
227:
228: FSID revnodeID = FSID.fromString(revNodeId);
229: if (revnodeID == null) {
230: SVNErrorMessage err = SVNErrorMessage.create(
231: SVNErrorCode.FS_CORRUPT,
232: "Corrupt node-id in node-rev");
233: SVNErrorManager.error(err);
234: }
235: revNode.setId(revnodeID);
236:
237: // Read the type.
238: SVNNodeKind nodeKind = SVNNodeKind.parseKind((String) headers
239: .get(FSRevisionNode.HEADER_TYPE));
240: if (nodeKind == SVNNodeKind.NONE
241: || nodeKind == SVNNodeKind.UNKNOWN) {
242: SVNErrorMessage err = SVNErrorMessage.create(
243: SVNErrorCode.FS_CORRUPT,
244: "Missing kind field in node-rev");
245: SVNErrorManager.error(err);
246: }
247: revNode.setType(nodeKind);
248:
249: // Read the 'count' field.
250: String countString = (String) headers
251: .get(FSRevisionNode.HEADER_COUNT);
252: if (countString == null) {
253: revNode.setCount(0);
254: } else {
255: long cnt = -1;
256: try {
257: cnt = Long.parseLong(countString);
258: } catch (NumberFormatException nfe) {
259: SVNErrorMessage err = SVNErrorMessage.create(
260: SVNErrorCode.FS_CORRUPT,
261: "Corrupt count field in node-rev");
262: SVNErrorManager.error(err);
263: }
264: revNode.setCount(cnt);
265: }
266:
267: // Get the properties location (if any).
268: String propsRepr = (String) headers
269: .get(FSRevisionNode.HEADER_PROPS);
270: if (propsRepr != null) {
271: parseRepresentationHeader(propsRepr, revNode, revnodeID
272: .getTxnID(), false);
273: }
274:
275: // Get the data location (if any).
276: String textRepr = (String) headers
277: .get(FSRevisionNode.HEADER_TEXT);
278: if (textRepr != null) {
279: parseRepresentationHeader(textRepr, revNode, revnodeID
280: .getTxnID(), true);
281: }
282:
283: // Get the created path.
284: String cpath = (String) headers
285: .get(FSRevisionNode.HEADER_CPATH);
286: if (cpath == null) {
287: SVNErrorMessage err = SVNErrorMessage.create(
288: SVNErrorCode.FS_CORRUPT,
289: "Missing cpath in node-rev");
290: SVNErrorManager.error(err);
291: }
292: revNode.setCreatedPath(cpath);
293:
294: // Get the predecessor rev-node id (if any).
295: String predId = (String) headers
296: .get(FSRevisionNode.HEADER_PRED);
297: if (predId != null) {
298: FSID predRevNodeId = FSID.fromString(predId);
299: if (predRevNodeId == null) {
300: SVNErrorMessage err = SVNErrorMessage.create(
301: SVNErrorCode.FS_CORRUPT,
302: "Corrupt predecessor node-id in node-rev");
303: SVNErrorManager.error(err);
304: }
305: revNode.setPredecessorId(predRevNodeId);
306: }
307:
308: // Get the copyroot.
309: String copyroot = (String) headers
310: .get(FSRevisionNode.HEADER_COPYROOT);
311: if (copyroot == null) {
312: revNode.setCopyRootPath(revNode.getCreatedPath());
313: revNode.setCopyRootRevision(revNode.getCreatedRevision());
314: } else {
315: parseCopyRoot(copyroot, revNode);
316: }
317:
318: // Get the copyfrom.
319: String copyfrom = (String) headers
320: .get(FSRevisionNode.HEADER_COPYFROM);
321: if (copyfrom == null) {
322: revNode.setCopyFromPath(null);
323: revNode
324: .setCopyFromRevision(FSRepository.SVN_INVALID_REVNUM);
325: } else {
326: parseCopyFrom(copyfrom, revNode);
327: }
328: revNode.myIsFreshTxnRoot = headers
329: .containsKey(HEADER_IS_FRESH_TXN_ROOT);
330: return revNode;
331: }
332:
333: private static void parseCopyFrom(String copyfrom,
334: FSRevisionNode revNode) throws SVNException {
335: if (copyfrom == null || copyfrom.length() == 0) {
336: SVNErrorMessage err = SVNErrorMessage.create(
337: SVNErrorCode.FS_CORRUPT,
338: "Malformed copyfrom line in node-rev");
339: SVNErrorManager.error(err);
340: }
341:
342: int delimiterInd = copyfrom.indexOf(' ');
343: if (delimiterInd == -1) {
344: SVNErrorMessage err = SVNErrorMessage.create(
345: SVNErrorCode.FS_CORRUPT,
346: "Malformed copyfrom line in node-rev");
347: SVNErrorManager.error(err);
348: }
349:
350: String copyfromRev = copyfrom.substring(0, delimiterInd);
351: String copyfromPath = copyfrom.substring(delimiterInd + 1);
352:
353: long rev = -1;
354: try {
355: rev = Long.parseLong(copyfromRev);
356: } catch (NumberFormatException nfe) {
357: SVNErrorMessage err = SVNErrorMessage.create(
358: SVNErrorCode.FS_CORRUPT,
359: "Malformed copyfrom line in node-rev");
360: SVNErrorManager.error(err);
361: }
362: revNode.setCopyFromRevision(rev);
363: revNode.setCopyFromPath(copyfromPath);
364: }
365:
366: private static void parseCopyRoot(String copyroot,
367: FSRevisionNode revNode) throws SVNException {
368: if (copyroot == null || copyroot.length() == 0) {
369: SVNErrorMessage err = SVNErrorMessage.create(
370: SVNErrorCode.FS_CORRUPT,
371: "Malformed copyroot line in node-rev");
372: SVNErrorManager.error(err);
373: }
374:
375: int delimiterInd = copyroot.indexOf(' ');
376: if (delimiterInd == -1) {
377: SVNErrorMessage err = SVNErrorMessage.create(
378: SVNErrorCode.FS_CORRUPT,
379: "Malformed copyroot line in node-rev");
380: SVNErrorManager.error(err);
381: }
382:
383: String copyrootRev = copyroot.substring(0, delimiterInd);
384: String copyrootPath = copyroot.substring(delimiterInd + 1);
385:
386: long rev = -1;
387: try {
388: rev = Long.parseLong(copyrootRev);
389: } catch (NumberFormatException nfe) {
390: SVNErrorMessage err = SVNErrorMessage.create(
391: SVNErrorCode.FS_CORRUPT,
392: "Malformed copyroot line in node-rev");
393: SVNErrorManager.error(err);
394: }
395: revNode.setCopyRootRevision(rev);
396: revNode.setCopyRootPath(copyrootPath);
397: }
398:
399: private static void parseRepresentationHeader(
400: String representation, FSRevisionNode revNode,
401: String txnId, boolean isData) throws SVNException {
402: if (revNode == null) {
403: return;
404: }
405:
406: FSRepresentation rep = new FSRepresentation();
407:
408: int delimiterInd = representation.indexOf(' ');
409: String revision = null;
410: if (delimiterInd == -1) {
411: revision = representation;
412: } else {
413: revision = representation.substring(0, delimiterInd);
414: }
415:
416: long rev = -1;
417: try {
418: rev = Long.parseLong(revision);
419: } catch (NumberFormatException nfe) {
420: SVNErrorMessage err = SVNErrorMessage.create(
421: SVNErrorCode.FS_CORRUPT,
422: "Malformed text rep offset line in node-rev");
423: SVNErrorManager.error(err);
424: }
425: rep.setRevision(rev);
426:
427: if (FSRepository.isInvalidRevision(rep.getRevision())) {
428: rep.setTxnId(txnId);
429: if (isData) {
430: revNode.setTextRepresentation(rep);
431: } else {
432: revNode.setPropsRepresentation(rep);
433: }
434: // is it a mutable representation?
435: if (!isData || revNode.getType() == SVNNodeKind.DIR) {
436: return;
437: }
438: }
439:
440: representation = representation.substring(delimiterInd + 1);
441:
442: delimiterInd = representation.indexOf(' ');
443: if (delimiterInd == -1) {
444: SVNErrorMessage err = SVNErrorMessage.create(
445: SVNErrorCode.FS_CORRUPT,
446: "Malformed text rep offset line in node-rev");
447: SVNErrorManager.error(err);
448: }
449: String repOffset = representation.substring(0, delimiterInd);
450:
451: long offset = -1;
452: try {
453: offset = Long.parseLong(repOffset);
454: if (offset < 0) {
455: throw new NumberFormatException();
456: }
457: } catch (NumberFormatException nfe) {
458: SVNErrorMessage err = SVNErrorMessage.create(
459: SVNErrorCode.FS_CORRUPT,
460: "Malformed text rep offset line in node-rev");
461: SVNErrorManager.error(err);
462: }
463: rep.setOffset(offset);
464:
465: representation = representation.substring(delimiterInd + 1);
466: delimiterInd = representation.indexOf(' ');
467: if (delimiterInd == -1) {
468: SVNErrorMessage err = SVNErrorMessage.create(
469: SVNErrorCode.FS_CORRUPT,
470: "Malformed text rep offset line in node-rev");
471: SVNErrorManager.error(err);
472: }
473: String repSize = representation.substring(0, delimiterInd);
474:
475: long size = -1;
476: try {
477: size = Long.parseLong(repSize);
478: if (size < 0) {
479: throw new NumberFormatException();
480: }
481: } catch (NumberFormatException nfe) {
482: SVNErrorMessage err = SVNErrorMessage.create(
483: SVNErrorCode.FS_CORRUPT,
484: "Malformed text rep offset line in node-rev");
485: SVNErrorManager.error(err);
486: }
487: rep.setSize(size);
488:
489: representation = representation.substring(delimiterInd + 1);
490: delimiterInd = representation.indexOf(' ');
491: if (delimiterInd == -1) {
492: SVNErrorMessage err = SVNErrorMessage.create(
493: SVNErrorCode.FS_CORRUPT,
494: "Malformed text rep offset line in node-rev");
495: SVNErrorManager.error(err);
496: }
497: String repExpandedSize = representation.substring(0,
498: delimiterInd);
499:
500: long expandedSize = -1;
501: try {
502: expandedSize = Long.parseLong(repExpandedSize);
503: if (expandedSize < 0) {
504: throw new NumberFormatException();
505: }
506: } catch (NumberFormatException nfe) {
507: SVNErrorMessage err = SVNErrorMessage.create(
508: SVNErrorCode.FS_CORRUPT,
509: "Malformed text rep offset line in node-rev");
510: SVNErrorManager.error(err);
511: }
512: rep.setExpandedSize(expandedSize);
513:
514: String hexDigest = representation.substring(delimiterInd + 1);
515: if (hexDigest.length() != 32
516: || SVNFileUtil.fromHexDigest(hexDigest) == null) {
517: SVNErrorMessage err = SVNErrorMessage.create(
518: SVNErrorCode.FS_CORRUPT,
519: "Malformed text rep offset line in node-rev");
520: SVNErrorManager.error(err);
521: }
522: rep.setHexDigest(hexDigest);
523: if (isData) {
524: revNode.setTextRepresentation(rep);
525: } else {
526: revNode.setPropsRepresentation(rep);
527: }
528: }
529:
530: public FSRevisionNode getChildDirNode(String childName,
531: FSFS fsfsOwner) throws SVNException {
532: if (!SVNPathUtil.isSinglePathComponent(childName)) {
533: SVNErrorMessage err = SVNErrorMessage
534: .create(
535: SVNErrorCode.FS_NOT_SINGLE_PATH_COMPONENT,
536: "Attempted to open node with an illegal name ''{0}''",
537: childName);
538: SVNErrorManager.error(err);
539: }
540:
541: Map entries = getDirEntries(fsfsOwner);
542: FSEntry entry = entries != null ? (FSEntry) entries
543: .get(childName) : null;
544:
545: if (entry == null) {
546: SVNErrorMessage err = SVNErrorMessage
547: .create(
548: SVNErrorCode.FS_NOT_FOUND,
549: "Attempted to open non-existent child node ''{0}''",
550: childName);
551: SVNErrorManager.error(err);
552: }
553:
554: return fsfsOwner.getRevisionNode(entry.getId());
555: }
556:
557: public Map getDirEntries(FSFS fsfsOwner) throws SVNException {
558: if (getType() != SVNNodeKind.DIR) {
559: SVNErrorMessage err = SVNErrorMessage.create(
560: SVNErrorCode.FS_NOT_DIRECTORY,
561: "Can't get entries of non-directory");
562: SVNErrorManager.error(err);
563: }
564: Map dirContents = getDirContents();
565:
566: if (dirContents == null) {
567: dirContents = fsfsOwner.getDirContents(this );
568: setDirContents(dirContents);
569: }
570: return Collections.unmodifiableMap(dirContents);
571: }
572:
573: public Map getProperties(FSFS fsfsOwner) throws SVNException {
574: return fsfsOwner.getProperties(this );
575: }
576:
577: public FSRepresentation chooseDeltaBase(FSFS fsfsOwner)
578: throws SVNException {
579: if (getCount() == 0) {
580: return null;
581: }
582:
583: long count = getCount();
584: count = count & (count - 1);
585: FSRevisionNode baseNode = this ;
586: while ((count++) < getCount()) {
587: baseNode = fsfsOwner.getRevisionNode(baseNode
588: .getPredecessorId());
589: }
590: return baseNode.getTextRepresentation();
591: }
592:
593: public String getFileChecksum() throws SVNException {
594: if (getType() != SVNNodeKind.FILE) {
595: SVNErrorMessage err = SVNErrorMessage.create(
596: SVNErrorCode.FS_NOT_FILE,
597: "Attempted to get checksum of a *non*-file node");
598: SVNErrorManager.error(err);
599: }
600: return getTextRepresentation() != null ? getTextRepresentation()
601: .getHexDigest()
602: : "";
603: }
604:
605: public long getFileLength() throws SVNException {
606: if (getType() != SVNNodeKind.FILE) {
607: SVNErrorMessage err = SVNErrorMessage.create(
608: SVNErrorCode.FS_NOT_FILE,
609: "Attempted to get length of a *non*-file node");
610: SVNErrorManager.error(err);
611: }
612: return getTextRepresentation() != null ? getTextRepresentation()
613: .getExpandedSize()
614: : 0;
615: }
616:
617: public void setIsFreshTxnRoot(boolean isFreshTxnRoot) {
618: myIsFreshTxnRoot = isFreshTxnRoot;
619: }
620:
621: public boolean isFreshTxnRoot() {
622: return myIsFreshTxnRoot;
623: }
624:
625: public void setFreshRootPredecessorId(FSID freshRootPredecessorId) {
626: myFreshRootPredecessorId = freshRootPredecessorId;
627: }
628:
629: }
|