/* * ==================================================================== * 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.io.fs; import java.io.InputStream; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public abstract class FSRoot { private RevisionCache myRevNodesCache; private FSFS myFSFS; protected FSRevisionNode myRootRevisionNode; protected FSRoot(FSFS owner) { myFSFS = owner; } public FSFS getOwner() { return myFSFS; } public FSRevisionNode getRevisionNode(String path) throws SVNException { path = SVNPathUtil.canonicalizeAbsolutePath(path); FSRevisionNode node = fetchRevNodeFromCache(path); if (node == null) { FSParentPath parentPath = openPath(path, true, false); node = parentPath.getRevNode(); } return node; } public abstract long getRevision(); public abstract FSRevisionNode getRootRevisionNode() throws SVNException; public abstract Map getChangedPaths() throws SVNException; public abstract FSCopyInheritance getCopyInheritance(FSParentPath child) throws SVNException; public FSParentPath openPath(String path, boolean lastEntryMustExist, boolean storeParents) throws SVNException { if (path == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "null path is not supported"); SVNErrorManager.error(err, SVNLogType.FSFS); } String canonPath = SVNPathUtil.canonicalizeAbsolutePath(path); FSRevisionNode here = getRootRevisionNode(); String pathSoFar = "/"; FSParentPath parentPath = new FSParentPath(here, null, null); parentPath.setCopyStyle(FSCopyInheritance.COPY_ID_INHERIT_SELF); // skip the leading '/' String rest = canonPath.substring(1); while (true) { String entry = SVNPathUtil.head(rest); String next = SVNPathUtil.removeHead(rest); pathSoFar = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(pathSoFar, entry)); FSRevisionNode child = null; if (entry == null || "".equals(entry)) { child = here; } else { FSRevisionNode cachedRevNode = fetchRevNodeFromCache(pathSoFar); if (cachedRevNode != null) { child = cachedRevNode; } else { try { child = here.getChildDirNode(entry, getOwner()); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { if (!lastEntryMustExist && (next == null || "".equals(next))) { return new FSParentPath(null, entry, parentPath); } SVNErrorManager.error(FSErrors.errorNotFound(this, path), svne, SVNLogType.FSFS); } throw svne; } } parentPath.setParentPath(child, entry, storeParents ? new FSParentPath(parentPath) : null); if (storeParents) { FSCopyInheritance copyInheritance = getCopyInheritance(parentPath); if (copyInheritance != null) { parentPath.setCopyStyle(copyInheritance.getStyle()); parentPath.setCopySourcePath(copyInheritance.getCopySourcePath()); } } if (cachedRevNode == null) { putRevNodeToCache(pathSoFar, child); } } if (next == null || "".equals(next)) { break; } if (child.getType() != SVNNodeKind.DIR) { SVNErrorMessage err = FSErrors.errorNotDirectory(pathSoFar, getOwner()); SVNErrorManager.error(err.wrap("Failure opening ''{0}''", path), SVNLogType.FSFS); } rest = next; here = child; } return parentPath; } public SVNNodeKind checkNodeKind(String path) throws SVNException { FSRevisionNode revNode = null; try { revNode = getRevisionNode(path); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND || svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_DIRECTORY) { return SVNNodeKind.NONE; } throw svne; } return revNode.getType(); } public void putRevNodeToCache(String path, FSRevisionNode node) throws SVNException { if (!path.startsWith("/")) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Invalid path ''{0}''", path); SVNErrorManager.error(err, SVNLogType.FSFS); } if (myRevNodesCache == null) { myRevNodesCache = new RevisionCache(100); } myRevNodesCache.put(path, node); } public void removeRevNodeFromCache(String path) throws SVNException { if (!path.startsWith("/")) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Invalid path ''{0}''", path); SVNErrorManager.error(err, SVNLogType.FSFS); } if (myRevNodesCache == null) { return; } myRevNodesCache.delete(path); } protected FSRevisionNode fetchRevNodeFromCache(String path) throws SVNException { if (myRevNodesCache == null) { return null; } if (!path.startsWith("/")) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Invalid path ''{0}''", path); SVNErrorManager.error(err, SVNLogType.FSFS); } return (FSRevisionNode) myRevNodesCache.fetch(path); } private void foldChange(Map mapChanges, FSPathChange change) throws SVNException { if (change == null) { return; } mapChanges = mapChanges != null ? mapChanges : new SVNHashMap(); FSPathChange newChange = null; String copyfromPath = null; long copyfromRevision = SVNRepository.INVALID_REVISION; FSPathChange oldChange = (FSPathChange) mapChanges.get(change.getPath()); if (oldChange != null) { copyfromPath = oldChange.getCopyPath(); copyfromRevision = oldChange.getCopyRevision(); if ((change.getRevNodeId() == null) && (FSPathChangeKind.FS_PATH_CHANGE_RESET != change.getChangeKind())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Missing required node revision ID"); SVNErrorManager.error(err, SVNLogType.FSFS); } if ((change.getRevNodeId() != null) && (!oldChange.getRevNodeId().equals(change.getRevNodeId())) && (oldChange.getChangeKind() != FSPathChangeKind.FS_PATH_CHANGE_DELETE)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Invalid change ordering: new node revision ID without delete"); SVNErrorManager.error(err, SVNLogType.FSFS); } if (FSPathChangeKind.FS_PATH_CHANGE_DELETE == oldChange.getChangeKind() && !(FSPathChangeKind.FS_PATH_CHANGE_REPLACE == change.getChangeKind() || FSPathChangeKind.FS_PATH_CHANGE_RESET == change.getChangeKind() || FSPathChangeKind.FS_PATH_CHANGE_ADD == change .getChangeKind())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Invalid change ordering: non-add change on deleted path"); SVNErrorManager.error(err, SVNLogType.FSFS); } if (FSPathChangeKind.FS_PATH_CHANGE_MODIFY == change.getChangeKind()) { if (change.isTextModified()) { oldChange.setTextModified(true); } if (change.arePropertiesModified()) { oldChange.setPropertiesModified(true); } } else if (FSPathChangeKind.FS_PATH_CHANGE_ADD == change.getChangeKind() || FSPathChangeKind.FS_PATH_CHANGE_REPLACE == change.getChangeKind()) { oldChange.setChangeKind(FSPathChangeKind.FS_PATH_CHANGE_REPLACE); oldChange.setRevNodeId(change.getRevNodeId()); oldChange.setTextModified(change.isTextModified()); oldChange.setPropertiesModified(change.arePropertiesModified()); if (change.getCopyPath() != null) { copyfromPath = change.getCopyPath(); copyfromRevision = change.getCopyRevision(); } } else if (FSPathChangeKind.FS_PATH_CHANGE_DELETE == change.getChangeKind()) { if (FSPathChangeKind.FS_PATH_CHANGE_ADD == oldChange.getChangeKind()) { oldChange = null; mapChanges.remove(change.getPath()); } else { oldChange.setChangeKind(FSPathChangeKind.FS_PATH_CHANGE_DELETE); oldChange.setPropertiesModified(change.arePropertiesModified()); oldChange.setTextModified(change.isTextModified()); } copyfromPath = null; copyfromRevision = SVNRepository.INVALID_REVISION; } else if (FSPathChangeKind.FS_PATH_CHANGE_RESET == change.getChangeKind()) { oldChange = null; copyfromPath = null; copyfromRevision = SVNRepository.INVALID_REVISION; mapChanges.remove(change.getPath()); } newChange = oldChange; } else { copyfromPath = change.getCopyPath(); copyfromRevision = change.getCopyRevision(); newChange = change; } if (newChange != null) { newChange.setCopyPath(copyfromPath); newChange.setCopyRevision(copyfromRevision); final SVNNodeKind nodeKind = change.getKind(); if (nodeKind != null && nodeKind != SVNNodeKind.UNKNOWN) { newChange.setNodeKind(nodeKind); } mapChanges.put(change.getPath(), newChange); } } protected Map fetchAllChanges(FSFile changesFile, boolean prefolded) throws SVNException { Map changedPaths = new SVNHashMap(); FSPathChange change = readChange(changesFile); while (change != null) { foldChange(changedPaths, change); if ((FSPathChangeKind.FS_PATH_CHANGE_DELETE == change.getChangeKind() || FSPathChangeKind.FS_PATH_CHANGE_REPLACE == change.getChangeKind()) && !prefolded) { for (Iterator curIter = changedPaths.keySet().iterator(); curIter.hasNext();) { String hashKeyPath = (String) curIter.next(); if (change.getPath().equals(hashKeyPath)) { continue; } if (SVNPathUtil.getPathAsChild(change.getPath(), hashKeyPath) != null) { curIter.remove(); } } } change = readChange(changesFile); } return changedPaths; } public Map detectChanged() throws SVNException { Map changes = getChangedPaths(); if (changes.size() == 0) { return changes; } for (Iterator paths = changes.keySet().iterator(); paths.hasNext();) { String changedPath = (String) paths.next(); FSPathChange change = (FSPathChange) changes.get(changedPath); if (change.getChangeKind() == FSPathChangeKind.FS_PATH_CHANGE_RESET) { paths.remove(); } } return changes; } private FSPathChange readChange(FSFile raReader) throws SVNException { String changeLine = null; try { changeLine = raReader.readLine(4096); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.STREAM_UNEXPECTED_EOF) { return null; } throw svne; } if (changeLine == null || changeLine.length() == 0) { return null; } String copyfromLine = raReader.readLine(4096); return FSPathChange.fromString(changeLine, copyfromLine); } public InputStream getFileStreamForPath(SVNDeltaCombiner combiner, String path) throws SVNException { FSRevisionNode fileNode = getRevisionNode(path); return FSInputStream.createDeltaStream(combiner, fileNode, getOwner()); } public long getFileSize(String path) throws SVNException { FSRevisionNode fileNode = getRevisionNode(path); return fileNode.getFileLength(); } private static final class RevisionCache { private LinkedList myKeys; private Map myCache; private int mySizeLimit; public RevisionCache(int limit) { mySizeLimit = limit; myKeys = new LinkedList(); myCache = new TreeMap(); } public void put(Object key, Object value) { if (mySizeLimit <= 0) { return; } if (myKeys.size() == mySizeLimit) { Object cachedKey = myKeys.removeLast(); myCache.remove(cachedKey); } myKeys.addFirst(key); myCache.put(key, value); } public void delete(Object key) { myKeys.remove(key); myCache.remove(key); } public Object fetch(Object key) { int ind = myKeys.indexOf(key); if (ind != -1) { if (ind != 0) { Object cachedKey = myKeys.remove(ind); myKeys.addFirst(cachedKey); } return myCache.get(key); } return null; } } }