view src/org/tmatesoft/hg/core/RepositoryTreeWalker.java @ 79:5f9635c01681

TreeMap are more effective when keys are unique
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 25 Jan 2011 02:26:06 +0100
parents 6f1b88693d48
children bf304cb14247
line wrap: on
line source
/*
 * Copyright (c) 2011 TMate Software Ltd
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@svnkit.com
 */
package org.tmatesoft.hg.core;

import static org.tmatesoft.hg.repo.HgRepository.TIP;

import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import org.tmatesoft.hg.core.LogCommand.FileRevision;
import org.tmatesoft.hg.repo.HgManifest;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.util.PathPool;


/**
 *
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class RepositoryTreeWalker {
	
	private final HgRepository repo;
	private Path.Matcher matcher;
	private int startRev = 0, endRev = TIP;
	private Handler visitor;
	private boolean needDirs = false;
	
	private final Mediator mediator = new Mediator();

	public RepositoryTreeWalker(HgRepository hgRepo) {
		this.repo = hgRepo;
	}

	public RepositoryTreeWalker range(int rev1, int rev2) {
		// if manifest range is different from that of changelog, need conversion utils (external?)
		throw HgRepository.notImplemented();
	}
	
	public RepositoryTreeWalker revision(int rev) {
		startRev = endRev = rev;
		return this;
	}
	
	public RepositoryTreeWalker dirs(boolean include) {
		// XXX whether directories with directories only are include or not
		// now lists only directories with files
		needDirs = include;
		return this;
	}
	
	/**
	 * Limit manifest walk to a subset of files. 
	 * @param pathMatcher - filter, pass <code>null</code> to clear.
	 * @return <code>this</code> instance for convenience
	 */
	public RepositoryTreeWalker match(Path.Matcher pathMatcher) {
		matcher = pathMatcher;
		return this;
	}
	
	public void walk(Handler handler) {
		if (handler == null) {
			throw new IllegalArgumentException();
		}
		if (visitor != null) {
			throw new ConcurrentModificationException();
		}
		try {
			visitor = handler;
			mediator.start();
			repo.getManifest().walk(startRev, endRev, mediator);
		} finally {
			visitor = null;
			mediator.done();
		}
	}

	/**
	 * Callback to walk file/directory tree of a revision
	 */
	public interface Handler {
		void begin(Nodeid manifestRevision);
		void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
		void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
		void end(Nodeid manifestRevision);
	}

	// I'd rather let RepositoryTreeWalker implement HgManifest.Inspector directly, but this pollutes API alot
	private class Mediator implements HgManifest.Inspector {
		private PathPool pathPool;
		private List<FileRevision> manifestContent;
		private Nodeid manifestNodeid;
		
		public void start() {
			pathPool = new PathPool(repo.getPathHelper());
		}
		
		public void done() {
			manifestContent = null;
			pathPool = null;
		}
	
		public boolean begin(int revision, Nodeid nid) {
			if (needDirs && manifestContent == null) {
				manifestContent = new LinkedList<FileRevision>();
			}
			visitor.begin(manifestNodeid = nid);
			return true;
		}
		public boolean end(int revision) {
			if (needDirs) {
				LinkedHashMap<Path, LinkedList<FileRevision>> breakDown = new LinkedHashMap<Path, LinkedList<FileRevision>>();
				for (FileRevision fr : manifestContent) {
					Path filePath = fr.getPath();
					Path dirPath = pathPool.parent(filePath);
					LinkedList<FileRevision> revs = breakDown.get(dirPath);
					if (revs == null) {
						revs = new LinkedList<FileRevision>();
						breakDown.put(dirPath, revs);
					}
					revs.addLast(fr);
				}
				for (Path dir : breakDown.keySet()) {
					visitor.dir(dir);
					for (FileRevision fr : breakDown.get(dir)) {
						visitor.file(fr);
					}
				}
				manifestContent.clear();
			}
			visitor.end(manifestNodeid);
			manifestNodeid = null;
			return true;
		}
		public boolean next(Nodeid nid, String fname, String flags) {
			Path p = pathPool.path(fname);
			if (matcher != null && !matcher.accept(p)) {
				return true;
			}
			FileRevision fr = new FileRevision(repo, nid, p);
			if (needDirs) {
				manifestContent.add(fr);
			} else {
				visitor.file(fr);
			}
			return true;
		}
	}
}