/******************************************************************************* | |
* Copyright (c) 2011 Ericsson AB and others. | |
* All rights reserved. This program and the accompanying materials are | |
* made available under the terms of the Eclipse Public License v1.0 which | |
* accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* Ericsson AB - Initial API and Implementation | |
*******************************************************************************/ | |
package org.eclipse.mylyn.internal.subclipse.core; | |
import java.net.MalformedURLException; | |
import java.net.URI; | |
import java.text.ParseException; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.concurrent.ArrayBlockingQueue; | |
import java.util.concurrent.TimeUnit; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
import org.eclipse.core.filesystem.URIUtil; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.ILog; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.Path; | |
import org.eclipse.core.runtime.Status; | |
import org.eclipse.mylyn.versions.core.Change; | |
import org.eclipse.mylyn.versions.core.ChangeSet; | |
import org.eclipse.mylyn.versions.core.ChangeType; | |
import org.eclipse.mylyn.versions.core.ScmArtifact; | |
import org.eclipse.mylyn.versions.core.ScmRepository; | |
import org.eclipse.mylyn.versions.core.ScmUser; | |
import org.eclipse.mylyn.versions.core.spi.ScmConnector; | |
import org.eclipse.mylyn.versions.core.spi.ScmResourceUtils; | |
import org.eclipse.team.core.RepositoryProvider; | |
import org.eclipse.team.core.history.IFileRevision; | |
import org.tigris.subversion.subclipse.core.ISVNRemoteResource; | |
import org.tigris.subversion.subclipse.core.ISVNRepositoryLocation; | |
import org.tigris.subversion.subclipse.core.SVNException; | |
import org.tigris.subversion.subclipse.core.SVNProviderPlugin; | |
import org.tigris.subversion.subclipse.core.resources.SVNWorkspaceRoot; | |
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter; | |
import org.tigris.subversion.svnclientadapter.ISVNLogMessage; | |
import org.tigris.subversion.svnclientadapter.ISVNLogMessageChangePath; | |
import org.tigris.subversion.svnclientadapter.SVNClientException; | |
import org.tigris.subversion.svnclientadapter.SVNRevision; | |
import org.tigris.subversion.svnclientadapter.SVNRevision.Number; | |
import org.tigris.subversion.svnclientadapter.SVNUrl; | |
/** | |
* @author Alvaro Sanchez-Leon | |
*/ | |
@SuppressWarnings("restriction") | |
public class SubclipseConnector extends ScmConnector { | |
private final ILog logger = SubclipseCorePlugin.getDefault().getLog(); | |
private final Map<IProject, SubclipseRepository> mapProjToRepo = new HashMap<IProject, SubclipseRepository>(); | |
private Integer threadBookNum = new Integer(0); | |
/** | |
* allow mapping from local url project folders to works space project | |
*/ | |
private final Map<String, IProject> mapSvnFolderToProject = new HashMap<String, IProject>(); | |
@Override | |
public ScmArtifact getArtifact(IResource resource, String revision) { | |
ISVNRemoteResource svnResource = null; | |
SubclipseRepository repo = null; | |
try { | |
svnResource = SVNWorkspaceRoot.getBaseResourceFor(resource); | |
if (svnResource == null) { | |
//not in version control | |
return null; | |
} | |
repo = (SubclipseRepository) getRepository(resource, null); | |
} catch (SVNException e) { | |
// TODO: implement a plug-in logger besides the one in the work space | |
// e.printStackTrace(); | |
return null; | |
} catch (CoreException e) { | |
// TODO implement a plug-in logger besides the one in the work space | |
// e.printStackTrace(); | |
return null; | |
} | |
SVNRevision SubCRevision = svnResource.getRevision(); | |
String id = String.valueOf(SubCRevision.getKind()); | |
SubclipseArtifact artifact = new SubclipseArtifact(id, svnResource.getRepositoryRelativePath(), repo); | |
//Assign the resource information | |
artifact.setProjectName(resource.getProject().getName()); | |
artifact.setProjectRelativePath(resource.getProjectRelativePath().toPortableString()); | |
artifact.setRemoteResource(svnResource); | |
return artifact; | |
} | |
@Override | |
public ChangeSet getChangeSet(ScmRepository repository, IFileRevision revision, IProgressMonitor monitor) | |
throws CoreException { | |
//Cast to Access to the package implementation api | |
SubclipseRepository repo = (SubclipseRepository) repository; | |
//resolve the revision to SVNRevision | |
SVNRevision sRevision = resolveSvnRevision(revision); | |
return getChangeSet(repo, sRevision, monitor); | |
} | |
private ChangeSet getChangeSet(SubclipseRepository repository, SVNRevision sRevision, IProgressMonitor monitor) | |
throws CoreException { | |
//Get the commit message data for the single revision provided | |
SVNUrl repoLocationUrl = repository.getProjectSVNFolder(); | |
boolean fetchChangePaths = true; | |
ISVNLogMessage[] messages = resolveChangeSets(repository, repoLocationUrl, sRevision, sRevision, | |
fetchChangePaths, null); | |
if (messages == null) { | |
return null; | |
} | |
//Only one message is expected as the start and end revisions provided are the same | |
assert (messages.length == 1); | |
ISVNLogMessage isvnLogMessage = messages[0]; | |
List<Change> changes = buildChanges(repository, isvnLogMessage, true); | |
return changeSet(repository, isvnLogMessage, changes); | |
} | |
/** | |
* @param repository | |
* @param isvnLogMessage | |
* @param withBaseVersions | |
* - resolve base revisions | |
* @return | |
* @throws CoreException | |
*/ | |
private List<Change> buildChanges(SubclipseRepository repository, ISVNLogMessage isvnLogMessage, | |
boolean withBaseVersions) throws CoreException { | |
//Prepare the list of changes adapted from ISVNLogMessageChangePath | |
ISVNLogMessageChangePath[] changePaths = isvnLogMessage.getChangedPaths(); | |
List<Change> changes = new ArrayList<Change>(); | |
Number sRevision = isvnLogMessage.getRevision(); | |
//One Change instance created per changePath, needs to resolve base commit for each path | |
String id = String.valueOf(sRevision.getNumber()); | |
for (ISVNLogMessageChangePath isvnLogMessageChangePath : changePaths) { | |
//Resolve change type | |
ChangeType ctype = mapChangeType(isvnLogMessageChangePath); | |
//Initialise target and base artifact | |
SubclipseArtifact newArtifact = getArtifact(repository, isvnLogMessageChangePath, id); | |
SubclipseArtifact oldArtifact = null; | |
//TODO: Implement the resolution of the replaced version | |
if (ctype == ChangeType.ADDED || ctype == ChangeType.REPLACED) { | |
changes.add(new Change(oldArtifact, newArtifact, ctype)); | |
continue; | |
} | |
//Deep parsing is only needed when the item has been selected by the user | |
if (withBaseVersions) { | |
//Resolve the base artifact | |
try { | |
oldArtifact = resolveBaseArtifact(repository, newArtifact.getRepositoryURL(), sRevision, | |
isvnLogMessageChangePath, ctype); | |
} catch (MalformedURLException e) { | |
logger.log(new Status(IStatus.ERROR, SubclipseCorePlugin.PLUGIN_ID, | |
"Error resolving an artifact url" //$NON-NLS-1$ | |
+ isvnLogMessageChangePath.getPath(), e)); | |
} | |
//Only applicable when resolution of base is requested | |
if (ctype == ChangeType.DELETED) { | |
//Target element does not contain a valid revision where the deleted element existed. base version does | |
newArtifact = null; | |
} | |
} | |
if (newArtifact != null || oldArtifact != null) { | |
changes.add(new Change(oldArtifact, newArtifact, ctype)); | |
} | |
} | |
return changes; | |
} | |
@Override | |
public List<ChangeSet> getChangeSets(ScmRepository repository, IProgressMonitor monitor) throws CoreException { | |
SubclipseRepository repo = (SubclipseRepository) repository; | |
SVNRevision firstRevision = new SVNRevision.Number(1L); | |
//TODO: retrieving all revisions per project repository can take really long time, | |
//need to discuss API to narrow down the list e.g. return an iterator | |
//For the time being we limit the possibility to review commits within the last 20 | |
ISVNLogMessage[] messages = resolveChangeSets(repo, repo.getProjectSVNFolder(), SVNRevision.HEAD, | |
firstRevision, false, 20L); | |
//Convert the messages to ChangeSet | |
List<ChangeSet> changeSets = new ArrayList<ChangeSet>(messages.length); | |
//No detailed changes provided at this point, but when requesting specific changeSet per revision | |
List<Change> changes = new ArrayList<Change>(); | |
//adapt to ChageSet | |
for (ISVNLogMessage message : messages) { | |
changeSets.add(changeSet(repo, message, changes)); | |
} | |
//resolve and keep references to all workspace subclipse project URLs | |
resolveSubclipseProjects(); | |
return changeSets; | |
} | |
@Override | |
public Iterator<ChangeSet> getChangeSetsIterator(ScmRepository repository, final IProgressMonitor monitor) { | |
//resolve and keep references to all workspace subclipse project URLs | |
resolveSubclipseProjects(); | |
final SubclipseRepository repo = (SubclipseRepository) repository; | |
final ChangeSetsIterator niterator = scheduleIterator(monitor, repo); | |
return niterator; | |
} | |
private ChangeSetsIterator scheduleIterator(final IProgressMonitor monitor, final SubclipseRepository repo) { | |
final ChangeSetsIterator niterator = new ChangeSetsIterator(repo, monitor); | |
Thread monitorThread = new Thread(new Runnable() { | |
public void run() { | |
//start potentially blocking task Using a blocking queue | |
Thread thread = new Thread(niterator); | |
String name = repo.getName() + "-" + ++threadBookNum; //$NON-NLS-1$ | |
thread.setName(name); | |
thread.start(); | |
niterator.setRunnableThread(thread); | |
try { | |
//monitor cancellation to make sure we unblock | |
//thread to be able to process the cancellation flag | |
while (thread.isAlive()) { | |
if (monitor.isCanceled()) { | |
thread.interrupt(); | |
break; | |
} | |
//Ugly. periodic poll for user cancellation | |
//TODO: Discuss the definition of mylyn versions implementation of an IProgressMonitor | |
//with an instance variable thread | |
//and Interrupt upon cancellation. Other options? | |
Thread.sleep(100); | |
} | |
thread.join(); | |
} catch (InterruptedException e) { | |
thread.interrupt(); | |
} | |
} | |
}); | |
monitorThread.start(); | |
return niterator; | |
} | |
@Override | |
public String getProviderId() { | |
return SVNProviderPlugin.getTypeId(); | |
} | |
protected SubclipseRepository getRepository(ISVNRepositoryLocation location, IProject project) { | |
SubclipseRepository repository = new SubclipseRepository(this, location, project); | |
return repository; | |
} | |
@Override | |
public ScmRepository getRepository(IResource resource, IProgressMonitor monitor) throws CoreException { | |
IProject project = resource.getProject(); | |
//check if it's cached | |
SubclipseRepository repo = mapProjToRepo.get(project); | |
if (repo == null) { | |
ISVNRepositoryLocation location = SVNWorkspaceRoot.getRepositoryFor(project.getLocation()); | |
repo = getRepository(location, project); | |
mapProjToRepo.put(project, repo); | |
} | |
return repo; | |
} | |
@Override | |
public List<ScmRepository> getRepositories(IProgressMonitor monitor) throws CoreException { | |
//We consider one repository per project in the workspace in order to limit the resolution of | |
//changesets to the ones associated to the selected project | |
if (mapSvnFolderToProject.size() == 0) { | |
resolveSubclipseProjects(); | |
} | |
List<ScmRepository> repositories = new ArrayList<ScmRepository>(mapSvnFolderToProject.size()); | |
for (IProject project : mapSvnFolderToProject.values()) { | |
ScmRepository repository = getRepository(project, monitor); | |
repositories.add(repository); | |
} | |
return repositories; | |
} | |
private SubclipseArtifact getArtifact(SubclipseRepository repo, ISVNLogMessageChangePath changePath, String id) { | |
SubclipseArtifact artifact = new SubclipseArtifact(id, changePath.getPath(), repo); | |
//ATTEMPT TO RESOLVE IT TO THE WORK SPACE | |
String artifactRepoURLStr = null; | |
try { | |
artifactRepoURLStr = artifact.getRepositoryURL().toString(); | |
} catch (MalformedURLException e) { | |
logger.log(new Status(IStatus.ERROR, SubclipseCorePlugin.PLUGIN_ID, "Unable to resolve URL", e)); //$NON-NLS-1$ | |
return null; | |
} | |
if (mapSvnFolderToProject.size() == 0) { | |
resolveSubclipseProjects(); | |
} | |
//Resolve an associated project in the work space | |
Set<String> projectFolders = mapSvnFolderToProject.keySet(); | |
for (String projURLStr : projectFolders) { | |
if (artifactRepoURLStr.startsWith(projURLStr)) { | |
//work space container project possibly found | |
//resolve the local file path of the artifact by replacing the project URL string in the artifact URL with the absolute path | |
IProject project = mapSvnFolderToProject.get(projURLStr); | |
String projectAbsPathStr = project.getLocation().toString(); | |
String artifactWSLocation = artifactRepoURLStr.replace(projURLStr, projectAbsPathStr); | |
//Attempt to resolve the IResource for it | |
URI absURI = URIUtil.toURI(new Path(artifactWSLocation)); | |
IFile ifile = ScmResourceUtils.getWorkSpaceFile(absURI, project); | |
if (ifile != null) { | |
//Artifact found in the work space | |
//Assign the eclipse project name and project relative path to the Artifact | |
artifact.setProjectName(project.getName()); | |
artifact.setProjectRelativePath(ifile.getProjectRelativePath().toPortableString()); | |
break; | |
} | |
} | |
} | |
return artifact; | |
} | |
private SubclipseArtifact resolveBaseArtifact(SubclipseRepository repo, SVNUrl pathUrl, Number sRevision, | |
ISVNLogMessageChangePath targetMessageChangePath, ChangeType ctype) throws CoreException { | |
SubclipseArtifact oldArtifact = null; | |
if (ctype == ChangeType.DELETED) { | |
//Deleted items can only resolve from previous version to deletion, as the item no longer exists | |
sRevision = new Number(sRevision.getNumber() - 1); | |
} | |
//Initialise the end revision to 1, although it will be limited to 2 entries to resolve the predecessor | |
SVNRevision eRevision = null; | |
try { | |
eRevision = SVNRevision.getRevision("1"); //$NON-NLS-1$ | |
} catch (ParseException e) { | |
//Should not happen for a constant value | |
e.printStackTrace(); | |
} | |
//Resolve the previous commit where this file took part | |
ISVNLogMessage[] filePreviousCommits = null; | |
boolean fetchChangePaths = true; | |
filePreviousCommits = resolveChangeSets(repo, pathUrl, sRevision, eRevision, fetchChangePaths, Long.valueOf(2L)); | |
String revisionId = null; | |
if (filePreviousCommits != null && filePreviousCommits.length > 1) { | |
ISVNLogMessage aBaseCommitMessage; | |
if (ctype != ChangeType.DELETED) { | |
//Position 0 is for the requested target, position one shall carry the base log message for the file | |
aBaseCommitMessage = filePreviousCommits[1]; | |
} else { | |
//For deleted items the request started from target version -1 | |
aBaseCommitMessage = filePreviousCommits[0]; | |
} | |
revisionId = aBaseCommitMessage.getRevision().toString(); | |
assert (revisionId != null); | |
oldArtifact = getArtifact(repo, targetMessageChangePath, revisionId); | |
} | |
return oldArtifact; | |
} | |
private SVNRevision resolveSvnRevision(IFileRevision revision) throws CoreException { | |
String versionId = revision.getContentIdentifier(); | |
SVNRevision sRevision = null; | |
try { | |
sRevision = SVNRevision.getRevision(versionId); | |
} catch (ParseException e1) { | |
IStatus status = new Status(IStatus.ERROR, SubclipseCorePlugin.PLUGIN_ID, "Unable to resolve VersionId " //$NON-NLS-1$ | |
+ versionId, e1); | |
throw new CoreException(status); | |
} | |
return sRevision; | |
} | |
private ChangeType mapChangeType(ISVNLogMessageChangePath isvnLogMessageChangePath) { | |
ChangeType changetype = null; | |
char action = isvnLogMessageChangePath.getAction(); | |
switch (action) { | |
case 'M': | |
changetype = ChangeType.MODIFIED; | |
break; | |
case 'A': | |
changetype = ChangeType.ADDED; | |
break; | |
case 'D': | |
changetype = ChangeType.DELETED; | |
break; | |
case 'R': | |
changetype = ChangeType.REPLACED; | |
break; | |
} | |
return changetype; | |
} | |
private ISVNLogMessage[] resolveChangeSets(SubclipseRepository repository, SVNUrl urlLocation, SVNRevision start, | |
SVNRevision end, boolean fetchChangePath, Long limit) throws CoreException { | |
ISVNRepositoryLocation location = repository.getLocation(); | |
ISVNLogMessage[] messages = null; | |
try { | |
ISVNClientAdapter adapter = location.getSVNClient(); | |
if (limit == null || limit < 1) { | |
messages = adapter.getLogMessages(urlLocation, start, end, fetchChangePath); | |
} else { | |
messages = adapter.getLogMessages(urlLocation, start, start, end, true, fetchChangePath, limit); | |
} | |
} catch (SVNClientException e) { | |
StringBuilder sb = new StringBuilder("Unable to resolve ChangeSet:" + start.toString() + " for location"); //$NON-NLS-1$ //$NON-NLS-2$ | |
if (urlLocation != null) { | |
sb.append(": " + urlLocation.toString()); //$NON-NLS-1$ | |
} | |
IStatus status = new Status(IStatus.ERROR, SubclipseCorePlugin.PLUGIN_ID, sb.toString(), e); | |
throw new CoreException(status); | |
} | |
return messages; | |
} | |
private ChangeSet changeSet(SubclipseRepository repository, ISVNLogMessage message, List<Change> changes) { | |
ChangeSet changeSet = null; | |
changeSet = new ChangeSet(getScmUser(message.getAuthor()), message.getDate(), message.getRevision().toString(), | |
message.getMessage(), repository, changes); | |
return changeSet; | |
} | |
private ScmUser getScmUser(String name) { | |
return new ScmUser("", name, ""); //$NON-NLS-1$//$NON-NLS-2$ | |
} | |
private void resolveSubclipseProjects() { | |
//refresh the list to allow for updates in the work space | |
mapSvnFolderToProject.clear(); | |
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); | |
for (IProject iProject : projects) { | |
RepositoryProvider provider = RepositoryProvider.getProvider(iProject); | |
if (provider != null && getProviderId().equals(provider.getID())) { | |
//found a subclipse project in the work space | |
String folderUrlStr = SVNWorkspaceRoot.getSVNFolderFor(iProject).getUrl().toString(); | |
mapSvnFolderToProject.put(folderUrlStr, iProject); | |
} | |
} | |
} | |
class ChangeSetsIterator implements Iterator<ChangeSet>, Runnable { | |
private final int QUEUE_MAX = 40; | |
private final Long CHUNKSIZE = 21L; | |
private boolean dataProcessingStarted = false; | |
private final SVNRevision earliestRevision = new SVNRevision.Number(1L); | |
private final ArrayBlockingQueue<ChangeSet> changeSetQueue = new ArrayBlockingQueue<ChangeSet>(QUEUE_MAX); | |
private final SubclipseRepository repo; | |
private volatile AtomicBoolean done = new AtomicBoolean(false); | |
private volatile AtomicBoolean cancelled = new AtomicBoolean(false); | |
private Thread thread = null; | |
private final IProgressMonitor monitor; | |
private final String[] EXIT_ERROR_MESSAGES = new String[] { "connection refused", "Connection timed out" }; | |
public ChangeSetsIterator(SubclipseRepository repository, IProgressMonitor aMonitor) { | |
this.repo = repository; | |
this.monitor = aMonitor; | |
} | |
public void run() { | |
ISVNLogMessage[] msgList = null; | |
ISVNLogMessage messageBeingProcessed = null; | |
Number headRevisionNum = new Number(Long.MAX_VALUE); | |
int failedAttemptsCount = 0; | |
//while head revision > earliest and ... | |
while ((headRevisionNum.compareTo(earliestRevision) == 1) && !cancelled.get() && !done.get()) { | |
messageBeingProcessed = null; | |
SVNRevision startRevision = null; | |
//initialise start Revision | |
if (dataProcessingStarted) { | |
startRevision = headRevisionNum; | |
} else { | |
startRevision = SVNRevision.HEAD; | |
} | |
try { | |
//Resolve the changes for the max chunk size | |
msgList = resolveChangeSets(repo, repo.getProjectSVNFolder(), startRevision, earliestRevision, | |
true, CHUNKSIZE); | |
//adapt to ChangeSet | |
int size = msgList.length; | |
for (int i = 0; i < size && !cancelled.get() && !done.get(); i++) { | |
messageBeingProcessed = msgList[i]; | |
if (i == CHUNKSIZE - 1) { | |
//Don't add last entry to the queue as this will be the start of the next chunk | |
continue; | |
} | |
ChangeSet changeset; | |
// ChangeSet changeset = getChangeSet(repo, messageBeingProcessed.getRevision(), monitor); | |
List<Change> changes = buildChanges(repo, messageBeingProcessed, false); | |
changeset = changeSet(repo, messageBeingProcessed, changes); | |
try { | |
changeSetQueue.put(changeset); | |
} catch (InterruptedException e) { | |
//Exit | |
cancelled.set(true); | |
monitor.done(); | |
return; | |
} | |
// UI is done | |
if (monitor.isCanceled()) { | |
cancelled.set(true); | |
} | |
} | |
//update chunk head revision | |
headRevisionNum = updateProcessingHead(msgList[size - 1], headRevisionNum, false); | |
//increment worked items | |
monitor.worked(1); | |
failedAttemptsCount = 0; | |
if (size < CHUNKSIZE) { | |
//no more items to fetch | |
done.set(true); | |
} | |
} catch (CoreException e) { | |
StringBuilder sb = new StringBuilder("Unable to resolve changeSets, "); | |
String cause = e.getCause().getMessage(); | |
for (String errMessage : EXIT_ERROR_MESSAGES) { | |
if (cause.contains(errMessage)) { | |
//Network connection problems, exit | |
cancelled.set(true); | |
sb.append(cause); | |
} | |
} | |
if (failedAttemptsCount == 0) { | |
e.printStackTrace(); | |
logger.log(new Status(IStatus.ERROR, SubclipseCorePlugin.PLUGIN_ID, sb.toString(), e)); | |
} | |
failedAttemptsCount++; | |
if (!cancelled.get()) { | |
//attempt to continue processing next message | |
if (headRevisionNum != SVNRevision.HEAD && failedAttemptsCount < 11) { | |
headRevisionNum = updateProcessingHead(messageBeingProcessed, headRevisionNum, true); | |
} else { | |
cancelled.set(true); | |
done.set(true); | |
} | |
} | |
} | |
// UI is done | |
if (monitor.isCanceled()) { | |
cancelled.set(true); | |
} | |
} | |
done.set(true); | |
monitor.done(); | |
} | |
private Number updateProcessingHead(ISVNLogMessage messageBeingProcessed, Number current, boolean next) { | |
long nextRevisionValue; | |
if (messageBeingProcessed != null) { | |
//Take the latest processed message to start the new request | |
nextRevisionValue = messageBeingProcessed.getRevision().getNumber(); | |
} else { | |
//if the last processed message is invalid then use the current chunk start | |
nextRevisionValue = current.getNumber(); | |
} | |
if (next) { | |
nextRevisionValue--; | |
} | |
dataProcessingStarted = true; | |
return new Number(nextRevisionValue); | |
} | |
public boolean hasNext() { | |
if (changeSetQueue.size() > 0) { | |
return true; | |
} | |
if (cancelled.get() || done.get()) { | |
return false; | |
} | |
return true; | |
} | |
public ChangeSet next() { | |
try { | |
return changeSetQueue.poll(60, TimeUnit.SECONDS); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
Thread.currentThread().interrupt(); | |
} | |
return null; | |
} | |
public void remove() { | |
changeSetQueue.poll(); | |
} | |
public Thread getRunnableThread() { | |
return thread; | |
} | |
public void setRunnableThread(Thread thread) { | |
this.thread = thread; | |
} | |
public void setCancelled(boolean cancelled) { | |
this.cancelled.set(cancelled); | |
} | |
public boolean isCancelled() { | |
return cancelled.get(); | |
} | |
} | |
} |