tikhomirov@64: /*
tikhomirov@64:  * Copyright (c) 2011 TMate Software Ltd
tikhomirov@64:  *  
tikhomirov@64:  * This program is free software; you can redistribute it and/or modify
tikhomirov@64:  * it under the terms of the GNU General Public License as published by
tikhomirov@64:  * the Free Software Foundation; version 2 of the License.
tikhomirov@64:  *
tikhomirov@64:  * This program is distributed in the hope that it will be useful,
tikhomirov@64:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
tikhomirov@64:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
tikhomirov@64:  * GNU General Public License for more details.
tikhomirov@64:  *
tikhomirov@64:  * For information on how to redistribute this software under
tikhomirov@64:  * the terms of a license other than GNU General Public License
tikhomirov@102:  * contact TMate Software at support@hg4j.com
tikhomirov@64:  */
tikhomirov@64: package org.tmatesoft.hg.core;
tikhomirov@64: 
tikhomirov@127: import static org.tmatesoft.hg.core.HgStatus.Kind.*;
tikhomirov@148: import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
tikhomirov@109: import static org.tmatesoft.hg.repo.HgRepository.*;
tikhomirov@68: 
tikhomirov@93: import java.util.ConcurrentModificationException;
tikhomirov@93: 
tikhomirov@128: import org.tmatesoft.hg.internal.ChangelogHelper;
tikhomirov@74: import org.tmatesoft.hg.repo.HgRepository;
tikhomirov@109: import org.tmatesoft.hg.repo.HgStatusCollector;
tikhomirov@93: import org.tmatesoft.hg.repo.HgStatusInspector;
tikhomirov@94: import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
tikhomirov@133: import org.tmatesoft.hg.util.Path;
tikhomirov@133: import org.tmatesoft.hg.util.Path.Matcher;
tikhomirov@64: 
tikhomirov@64: /**
tikhomirov@131:  * Command to obtain file status information, 'hg status' counterpart. 
tikhomirov@131:  * 
tikhomirov@64:  * @author Artem Tikhomirov
tikhomirov@64:  * @author TMate Software Ltd.
tikhomirov@64:  */
tikhomirov@131: public class HgStatusCommand {
tikhomirov@64: 	private final HgRepository repo;
tikhomirov@64: 
tikhomirov@68: 	private int startRevision = TIP;
tikhomirov@68: 	private int endRevision = WORKING_COPY; 
tikhomirov@93: 	
tikhomirov@93: 	private final Mediator mediator = new Mediator();
tikhomirov@64: 
tikhomirov@131: 	public HgStatusCommand(HgRepository hgRepo) { 
tikhomirov@68: 		repo = hgRepo;
tikhomirov@68: 		defaults();
tikhomirov@64: 	}
tikhomirov@64: 
tikhomirov@131: 	public HgStatusCommand defaults() {
tikhomirov@93: 		final Mediator m = mediator;
tikhomirov@93: 		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
tikhomirov@99: 		m.needCopies = m.needClean = m.needIgnored = false;
tikhomirov@64: 		return this;
tikhomirov@64: 	}
tikhomirov@131: 	public HgStatusCommand all() {
tikhomirov@93: 		final Mediator m = mediator;
tikhomirov@93: 		m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true;
tikhomirov@99: 		m.needCopies = m.needClean = m.needIgnored = true;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@68: 	
tikhomirov@64: 
tikhomirov@131: 	public HgStatusCommand modified(boolean include) {
tikhomirov@93: 		mediator.needModified = include;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@131: 	public HgStatusCommand added(boolean include) {
tikhomirov@93: 		mediator.needAdded = include;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@131: 	public HgStatusCommand removed(boolean include) {
tikhomirov@93: 		mediator.needRemoved = include;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@131: 	public HgStatusCommand deleted(boolean include) {
tikhomirov@93: 		mediator.needMissing = include;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@131: 	public HgStatusCommand unknown(boolean include) {
tikhomirov@93: 		mediator.needUnknown = include;
tikhomirov@68: 		return this;
tikhomirov@68: 	}
tikhomirov@131: 	public HgStatusCommand clean(boolean include) {
tikhomirov@93: 		mediator.needClean = include;
tikhomirov@64: 		return this;
tikhomirov@64: 	}
tikhomirov@131: 	public HgStatusCommand ignored(boolean include) {
tikhomirov@93: 		mediator.needIgnored = include;
tikhomirov@64: 		return this;
tikhomirov@64: 	}
tikhomirov@64: 	
tikhomirov@68: 	/**
tikhomirov@148: 	 * If set, either base:revision or base:workingdir
tikhomirov@68: 	 * to unset, pass {@link HgRepository#TIP} or {@link HgRepository#BAD_REVISION}
tikhomirov@148: 	 * @param revision - local revision number to base status from
tikhomirov@148: 	 * @return this for convenience
tikhomirov@148: 	 * @throws IllegalArgumentException when revision is negative or {@link HgRepository#WORKING_COPY} 
tikhomirov@68: 	 */
tikhomirov@131: 	public HgStatusCommand base(int revision) {
tikhomirov@148: 		if (revision == WORKING_COPY || wrongLocalRevision(revision)) {
tikhomirov@148: 			throw new IllegalArgumentException(String.valueOf(revision));
tikhomirov@68: 		}
tikhomirov@68: 		if (revision == BAD_REVISION) {
tikhomirov@68: 			revision = TIP;
tikhomirov@68: 		}
tikhomirov@64: 		startRevision = revision;
tikhomirov@64: 		return this;
tikhomirov@64: 	}
tikhomirov@64: 	
tikhomirov@68: 	/**
tikhomirov@68: 	 * Revision without base == --change
tikhomirov@68: 	 * Pass {@link HgRepository#WORKING_COPY} or {@link HgRepository#BAD_REVISION} to reset
tikhomirov@148: 	 * @param revision - non-negative local revision number, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP}  
tikhomirov@148: 	 * @return this for convenience
tikhomirov@148: 	 * @throws IllegalArgumentException if local revision number doesn't specify legitimate revision. 
tikhomirov@68: 	 */
tikhomirov@131: 	public HgStatusCommand revision(int revision) {
tikhomirov@68: 		if (revision == BAD_REVISION) {
tikhomirov@68: 			revision = WORKING_COPY;
tikhomirov@68: 		}
tikhomirov@148: 		if (wrongLocalRevision(revision)) {
tikhomirov@143: 			throw new IllegalArgumentException(String.valueOf(revision));
tikhomirov@143: 		}
tikhomirov@68: 		endRevision = revision;
tikhomirov@64: 		return this;
tikhomirov@64: 	}
tikhomirov@64: 	
tikhomirov@143: 	/**
tikhomirov@143: 	 * Shorthand for {@link #base(int) cmd.base(BAD_REVISION)}{@link #change(int) .revision(revision)}
tikhomirov@143: 	 *  
tikhomirov@143: 	 * @param revision compare given revision against its parent
tikhomirov@148: 	 * @return this for convenience
tikhomirov@143: 	 */
tikhomirov@143: 	public HgStatusCommand change(int revision) {
tikhomirov@143: 		base(BAD_REVISION);
tikhomirov@143: 		return revision(revision);
tikhomirov@143: 	}
tikhomirov@143: 	
tikhomirov@148: 	/**
tikhomirov@148: 	 * Limit status operation to certain sub-tree.
tikhomirov@148: 	 * 
tikhomirov@148: 	 * @param pathMatcher - matcher to use,  pass null/ to reset
tikhomirov@148: 	 * @return this for convenience
tikhomirov@148: 	 */
tikhomirov@131: 	public HgStatusCommand match(Path.Matcher pathMatcher) {
tikhomirov@93: 		mediator.matcher = pathMatcher;
tikhomirov@123: 		return this;
tikhomirov@64: 	}
tikhomirov@64: 
tikhomirov@131: 	public HgStatusCommand subrepo(boolean visit) {
tikhomirov@64: 		throw HgRepository.notImplemented();
tikhomirov@64: 	}
tikhomirov@93: 
tikhomirov@93: 	/**
tikhomirov@93: 	 * Perform status operation according to parameters set.
tikhomirov@93: 	 *  
tikhomirov@93: 	 * @param handler callback to get status information
tikhomirov@93: 	 * @throws IllegalArgumentException if handler is null
tikhomirov@93: 	 * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread)
tikhomirov@93: 	 */
tikhomirov@109: 	public void execute(Handler statusHandler) {
tikhomirov@109: 		if (statusHandler == null) {
tikhomirov@93: 			throw new IllegalArgumentException();
tikhomirov@93: 		}
tikhomirov@128: 		if (mediator.busy()) {
tikhomirov@93: 			throw new ConcurrentModificationException();
tikhomirov@93: 		}
tikhomirov@94: 		HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext
tikhomirov@93: //		PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext
tikhomirov@93: 		try {
tikhomirov@93: 			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
tikhomirov@93: 			// I may use number of files in either rev1 or rev2 manifest edition
tikhomirov@128: 			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
tikhomirov@93: 			if (endRevision == WORKING_COPY) {
tikhomirov@94: 				HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
tikhomirov@93: 				wcsc.setBaseRevisionCollector(sc);
tikhomirov@93: 				wcsc.walk(startRevision, mediator);
tikhomirov@68: 			} else {
tikhomirov@93: 				if (startRevision == TIP) {
tikhomirov@93: 					sc.change(endRevision, mediator);
tikhomirov@93: 				} else {
tikhomirov@93: 					sc.walk(startRevision, endRevision, mediator);
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		} finally {
tikhomirov@93: 			mediator.done();
tikhomirov@93: 		}
tikhomirov@93: 	}
tikhomirov@93: 
tikhomirov@109: 	public interface Handler {
tikhomirov@109: 		void handleStatus(HgStatus s);
tikhomirov@109: 	}
tikhomirov@109: 
tikhomirov@93: 	private class Mediator implements HgStatusInspector {
tikhomirov@93: 		boolean needModified;
tikhomirov@93: 		boolean needAdded;
tikhomirov@93: 		boolean needRemoved;
tikhomirov@93: 		boolean needUnknown;
tikhomirov@93: 		boolean needMissing;
tikhomirov@93: 		boolean needClean;
tikhomirov@93: 		boolean needIgnored;
tikhomirov@99: 		boolean needCopies;
tikhomirov@93: 		Matcher matcher;
tikhomirov@128: 		Handler handler;
tikhomirov@128: 		private ChangelogHelper logHelper;
tikhomirov@93: 
tikhomirov@93: 		Mediator() {
tikhomirov@93: 		}
tikhomirov@93: 		
tikhomirov@128: 		public void start(Handler h, ChangelogHelper changelogHelper) {
tikhomirov@128: 			handler = h;
tikhomirov@128: 			logHelper = changelogHelper;
tikhomirov@93: 		}
tikhomirov@128: 
tikhomirov@93: 		public void done() {
tikhomirov@128: 			handler = null;
tikhomirov@128: 			logHelper = null;
tikhomirov@128: 		}
tikhomirov@128: 		
tikhomirov@128: 		public boolean busy() {
tikhomirov@128: 			return handler != null;
tikhomirov@93: 		}
tikhomirov@93: 
tikhomirov@93: 		public void modified(Path fname) {
tikhomirov@93: 			if (needModified) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Modified, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@68: 			}
tikhomirov@68: 		}
tikhomirov@93: 		public void added(Path fname) {
tikhomirov@93: 			if (needAdded) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Added, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void removed(Path fname) {
tikhomirov@93: 			if (needRemoved) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Removed, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void copied(Path fnameOrigin, Path fnameAdded) {
tikhomirov@93: 			if (needCopies) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fnameAdded)) {
tikhomirov@164: 					// FIXME in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
tikhomirov@128: 					handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void missing(Path fname) {
tikhomirov@93: 			if (needMissing) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Missing, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void unknown(Path fname) {
tikhomirov@93: 			if (needUnknown) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void clean(Path fname) {
tikhomirov@93: 			if (needClean) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Clean, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@93: 		public void ignored(Path fname) {
tikhomirov@93: 			if (needIgnored) {
tikhomirov@93: 				if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: 					handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
tikhomirov@93: 				}
tikhomirov@93: 			}
tikhomirov@93: 		}
tikhomirov@64: 	}
tikhomirov@64: }