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