More release steps automated.

- Dockbook references to Spring Data Commons files get updated.
- Updating of notice.txt.
- Gradle projects get Spring Data Commons version and repositories updated.
- Added release commit detection and tagging for cleanup.

Upgraded to Spring Boot 1.0.2.RELEASE.
This commit is contained in:
Oliver Gierke
2014-04-30 20:40:35 +02:00
parent 02e945694d
commit cabacc002f
16 changed files with 481 additions and 40 deletions

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.0.RC4</version>
<version>1.0.2.RELEASE</version>
</parent>
<properties>

View File

@@ -19,8 +19,10 @@ import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.docs.DocumentationOperations;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.git.Tags;
import org.springframework.data.release.gradle.GradleOperations;
import org.springframework.data.release.maven.MavenOperations;
import org.springframework.data.release.maven.Pom;
import org.springframework.data.release.misc.ReleaseOperations;
@@ -43,8 +45,10 @@ import org.springframework.stereotype.Component;
public class ReleaseCommands implements CommandMarker {
private final MavenOperations maven;
private final GradleOperations gradle;
private final GitOperations git;
private final ReleaseOperations misc;
private final DocumentationOperations docs;
@CliCommand("release predict")
public String predictTrainAndIteration() throws Exception {
@@ -95,18 +99,24 @@ public class ReleaseCommands implements CommandMarker {
public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
git.prepare(iteration);
misc.prepareChangelogs(iteration);
misc.updateResources(iteration);
docs.updateDockbookIncludes(iteration);
maven.updatePom(iteration, Phase.PREPARE);
gradle.updateProject(iteration, Phase.PREPARE);
}
@CliCommand(value = "release conclude")
public void conclude(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
// - pull updates
// - tag release
git.tagRelease(iteration);
// - post release pom updates
maven.updatePom(iteration, Phase.CLEANUP);
gradle.updateProject(iteration, Phase.CLEANUP);
// - push
}

View File

@@ -65,11 +65,6 @@ public class TrainIterationConverter implements Converter<TrainIteration> {
for (Train train : ReleaseTrains.TRAINS) {
// if (!StringUtils.hasText(existingData) &&
// !train.getName().toLowerCase().startsWith(existingData.toLowerCase())) {
// continue;
// }
for (Iteration iteration : train.getIterations()) {
completions.add(new Completion(new TrainIteration(train, iteration).toString()));
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.docs;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.git.GitProject;
import org.springframework.data.release.git.Tag;
import org.springframework.data.release.git.Tags;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.stereotype.Component;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DocumentationOperations {
private static final String INDEX_LOCATION = "/src/docbkx/index.xml";
private final Workspace workspace;
private final GitOperations git;
public void updateDockbookIncludes(TrainIteration iteration) throws Exception {
Tags tags = git.getTags(COMMONS);
ModuleIteration commons = iteration.getModule(COMMONS);
ModuleIteration previousIteration = iteration.getPreviousIteration(commons);
final GitProject gitProject = git.getGitProject(COMMONS);
final Tag previousTag = tags.createTag(previousIteration);
final Tag newTag = tags.createTag(commons);
for (ModuleIteration module : iteration) {
Project project = module.getProject();
if (!project.dependsOn(COMMONS)) {
continue;
}
workspace.processFile(INDEX_LOCATION, project, new LineCallback() {
@Override
public String doWith(String line, long number) {
boolean isInclude = line.contains("xi:include");
boolean containsGitRepo = line.contains(gitProject.getRepositoryName());
return isInclude && containsGitRepo ? line.replace(previousTag.toString(), newTag.toString()) : line;
}
});
}
}
}

View File

@@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
@@ -55,9 +56,14 @@ public class GitCommands implements CommandMarker {
return StringUtils.collectionToDelimitedString(git.getTags(project).asList(), "\n");
}
/**
* Resets all projects contained in the given {@link Train}.
*
* @param trainName
* @throws Exception
*/
@CliCommand("git reset")
public void reset(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption(key = "iteration",
mandatory = true) String iterationName) throws Exception {
public void reset(@CliOption(key = { "", "train" }, mandatory = true) String trainName) throws Exception {
git.reset(ReleaseTrains.getTrainByName(trainName));
}

View File

@@ -27,6 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.CommandResult;
import org.springframework.data.release.io.OsCommandOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.jira.IssueTracker;
import org.springframework.data.release.jira.Ticket;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
@@ -35,6 +37,7 @@ import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -52,6 +55,7 @@ public class GitOperations {
private final OsCommandOperations osCommandOperations;
private final Workspace workspace;
private final Logger logger;
private final PluginRegistry<IssueTracker, Project> issueTracker;
public GitProject getGitProject(Project project) {
return new GitProject(project, server);
@@ -168,6 +172,49 @@ public class GitOperations {
return new Tags(tags);
}
public void tagRelease(TrainIteration iteration) throws Exception {
for (ModuleIteration module : iteration) {
Branch branch = Branch.from(module);
Project project = module.getProject();
String checkoutCommand = String.format("git checkout %s", branch);
osCommandOperations.executeCommand(checkoutCommand, project).get();
String updateCommand = String.format("git pull origin %s", branch);
osCommandOperations.executeCommand(updateCommand, project).get();
String hash = getReleaseHash(module);
Tag tag = getTags(project).createTag(module);
String tagCommand = String.format("git tag %s %s", tag, hash);
osCommandOperations.executeCommand(tagCommand, project).get();
}
}
private String getReleaseHash(ModuleIteration module) throws Exception {
Project project = module.getProject();
String result = osCommandOperations.executeForResult("git log --pretty=format:'%h %s'", project);
Ticket releaseTicket = issueTracker.getPluginFor(project).getReleaseTicketFor(module);
String trigger = String.format("%s - Release", releaseTicket.getId());
logger.log(project, "Looking up release commit (ticket id %s)", releaseTicket.getId());
for (String line : result.split("\n")) {
int summaryStart = line.indexOf(" ");
if (line.substring(summaryStart + 1).startsWith(trigger)) {
return line.substring(0, summaryStart);
}
}
throw new IllegalStateException(String.format("Did not find a release commit for project %s (ticket id %s)",
project, releaseTicket.getId()));
}
/**
* Returns the {@link Tag} that represents the {@link ArtifactVersion} of the given {@link Project}.
*

View File

@@ -45,6 +45,16 @@ public class Tag implements Comparable<Tag> {
return ArtifactVersion.parse(getVersionSource());
}
/**
* Creates a new {@link Tag} for the given {@link ArtifactVersion} based on the format of the current one.
*
* @param version
* @return
*/
public Tag createNew(ArtifactVersion version) {
return new Tag(name.startsWith("v") ? "v".concat(version.toString()) : version.toString());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -22,6 +22,8 @@ import java.util.List;
import lombok.EqualsAndHashCode;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert;
/**
@@ -58,6 +60,10 @@ public class Tags implements Iterable<Tag> {
return tags.get(0);
}
public Tag createTag(ModuleIteration iteration) {
return getLatest().createNew(ArtifactVersion.from(iteration));
}
/**
* Returns all {@link Tag}s as {@link List}.
*

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.gradle;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.maven.Repository;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
/**
* Gradle specific operations.
*
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GradleOperations {
private static final String BUILD_GRADLE = "build.gradle";
private static final String GRADLE_PROPERTIES = "gradle.properties";
private static final String COMMONS_PROPERTY = "springDataCommonsVersion";
private final Workspace workspace;
private final Logger logger;
/**
* Updates all Gradle projects contained in the release.
*
* @param iteration
* @param phase
* @throws Exception
*/
public void updateProject(TrainIteration iteration, final Phase phase) throws Exception {
final Repository repository = new Repository(iteration.getIteration());
final ArtifactVersion commonsVersion = iteration.getModuleVersion(COMMONS);
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
final Project project = module.getProject();
if (!isGradleProject(project)) {
continue;
}
workspace.processFile(GRADLE_PROPERTIES, project, new LineCallback() {
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long)
*/
@Override
public String doWith(String line, long number) {
if (!line.contains(COMMONS_PROPERTY)) {
return line;
}
ArtifactVersion version = phase.equals(Phase.PREPARE) ? commonsVersion : commonsVersion
.getNextDevelopmentVersion();
logger.log(project, "Setting Spring Data Commons version in %s to %s.", GRADLE_PROPERTIES, version);
return String.format("%s=%s", COMMONS_PROPERTY, version);
}
});
workspace.processFile(BUILD_GRADLE, project, new LineCallback() {
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long)
*/
@Override
public String doWith(String line, long number) {
String snapshotUrl = repository.getSnapshotUrl();
String releaseUrl = repository.getUrl();
String message = "Switching to Spring repository %s";
switch (phase) {
case CLEANUP:
logger.log(project, message, snapshotUrl);
return line.contains(releaseUrl) ? line.replace(releaseUrl, snapshotUrl) : line;
case PREPARE:
default:
logger.log(project, message, releaseUrl);
return line.contains(snapshotUrl) ? line.replace(snapshotUrl, releaseUrl) : line;
}
}
});
}
}
/**
* Returns whether the given project is a Gradle project (checks for the presence of a build.gradle file).
*
* @param project
* @return
*/
private boolean isGradleProject(Project project) {
return workspace.getFile(BUILD_GRADLE, project).exists();
}
}

View File

@@ -17,8 +17,9 @@ package org.springframework.data.release.io;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Scanner;
import javax.annotation.PostConstruct;
@@ -30,6 +31,8 @@ import org.springframework.data.release.model.Project;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.google.common.io.Files;
/**
* Abstraction of the workspace that is used to work with the {@link Project}'s repositories, execute builds, etc.
*
@@ -39,6 +42,8 @@ import org.springframework.util.Assert;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Workspace {
private static final Charset UTF_8 = Charset.forName("UTF-8");
public static final String WORK_DIR_PROPERTY = "io.workDir";
private final Environment environment;
@@ -93,6 +98,40 @@ public class Workspace {
return new File(getProjectDirectory(project), name);
}
public boolean processFile(String filename, Project project, LineCallback callback) throws Exception {
File file = getFile(filename, project);
if (!file.exists()) {
return false;
}
StringBuilder builder = new StringBuilder();
try (Scanner scanner = new Scanner(file)) {
long number = 0;
while (scanner.hasNextLine()) {
String result = callback.doWith(scanner.nextLine(), number++);
if (result != null) {
builder.append(result).append("\n");
}
}
}
writeContentToFile(filename, project, builder.toString());
return true;
}
private void writeContentToFile(String name, Project project, String content) throws IOException {
File file = getFile(name, project);
Files.write(content, file, UTF_8);
}
/**
* Initializes the working directory and creates the folders if necessary.
*
@@ -103,8 +142,12 @@ public class Workspace {
Path path = getWorkingDirectory().toPath();
if (!Files.exists(path)) {
Files.createDirectories(path);
if (!java.nio.file.Files.exists(path)) {
java.nio.file.Files.createDirectories(path);
}
}
public interface LineCallback {
String doWith(String line, long number);
}
}

View File

@@ -31,10 +31,12 @@ import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO;
@@ -59,8 +61,25 @@ public class MavenOperations {
return projectionFactory.io().file(file).read(Pom.class);
}
/**
* Updates the POM files for all Maven projects contained in the iteration:
* <ol>
* <li>Updates the BOM POM.</li>
* <li>Updates the dependency version to Spring Data Commons to the current release version for all projects depending
* on it.</li>
* <li>Switches to the Spring release Maven repository.</li>
* </ol>
* If {@link Phase} is {@link Phase#CLEANUP} the changes will be rolled back.
*
* @param iteration must not be {@literal null}.
* @param phase must not be {@literal null}.
* @throws Exception
*/
public void updatePom(TrainIteration iteration, final Phase phase) throws Exception {
Assert.notNull(iteration, "Train iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!");
updateBomPom(iteration, phase);
final Repository repository = new Repository(iteration.getIteration());
@@ -70,20 +89,31 @@ public class MavenOperations {
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
final Project project = module.getProject();
File pomFile = workspace.getFile(POM_XML, project);
execute(pomFile, new PomCallback() {
if (!isMavenProject(project)) {
logger.log(module, "No pom.xml file found, skipping project.");
continue;
}
execute(workspace.getFile(POM_XML, project), new PomCallback() {
@Override
public Pom doWith(Pom pom) {
if (!project.equals(COMMONS)) {
pom.setProperty(COMMONS_VERSION_PROPERTY,
CLEANUP.equals(phase) ? commonsVersion.getNextDevelopmentVersion() : commonsVersion);
if (project.dependsOn(Projects.COMMONS)) {
ArtifactVersion version = CLEANUP.equals(phase) ? commonsVersion.getNextDevelopmentVersion()
: commonsVersion;
logger.log(project, "Updating Spring Data Commons version dependecy to %s (setting property %s).", version,
COMMONS_VERSION_PROPERTY);
pom.setProperty(COMMONS_VERSION_PROPERTY, version);
}
pom.setParentVersion(CLEANUP.equals(phase) ? buildVersion.getNextDevelopmentVersion() : buildVersion);
updateRepository(pom, repository, phase);
ArtifactVersion version = CLEANUP.equals(phase) ? buildVersion.getNextDevelopmentVersion() : buildVersion;
logger.log(project, "Updating Spring Data Build Parent version to %s.", version);
pom.setParentVersion(version);
updateRepository(project, pom, repository, phase);
return pom;
}
@@ -144,6 +174,8 @@ public class MavenOperations {
File bomPomFile = workspace.getFile("bom/pom.xml", BUILD);
logger.log(BUILD, "Updating BOM pom.xml…");
execute(bomPomFile, new PomCallback() {
@Override
@@ -155,6 +187,8 @@ public class MavenOperations {
ArtifactVersion version = artifact.getVersion();
version = PREPARE.equals(phase) ? version : version.getNextDevelopmentVersion();
logger.log(BUILD, "%s", module);
pom.setDependencyVersion(artifact.getArtifactId(), version);
}
@@ -163,12 +197,21 @@ public class MavenOperations {
});
}
private void updateRepository(Pom pom, Repository repository, Phase phase) {
private void updateRepository(Project project, Pom pom, Repository repository, Phase phase) {
String message = "Switching to Spring repository %s (%s).";
if (PREPARE.equals(phase)) {
logger.log(project, message, repository.getId(), repository.getUrl());
pom.setRepositoryId(repository.getSnapshotId(), repository.getId());
pom.setRepositoryUrl(repository.getId(), repository.getUrl());
} else {
logger.log(project, message, repository.getSnapshotId(), repository.getSnapshotUrl());
pom.setRepositoryId(repository.getId(), repository.getSnapshotId());
pom.setRepositoryUrl(repository.getSnapshotId(), repository.getSnapshotUrl());
}

View File

@@ -15,14 +15,15 @@
*/
package org.springframework.data.release.misc;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Scanner;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.jira.Changelog;
import org.springframework.data.release.jira.IssueTracker;
import org.springframework.data.release.model.Iteration;
@@ -30,12 +31,11 @@ import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.google.common.io.Files;
/**
* @author Oliver Gierke
*/
@@ -43,8 +43,20 @@ import com.google.common.io.Files;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ReleaseOperations {
private static final Set<String> CHANGELOG_LOCATIONS;
static {
Set<String> locations = new HashSet<>();
locations.add("src/main/resources/changelog.txt"); // for Maven projects
locations.add("docs/src/info/changelog.txt"); // for Gradle projects
CHANGELOG_LOCATIONS = Collections.unmodifiableSet(locations);
}
private final PluginRegistry<IssueTracker, Project> trackers;
private final Workspace workspace;
private final Logger logger;
/**
* Creates {@link Changelog} instances for all modules of the given {@link Train} and {@link Iteration}.
@@ -59,26 +71,53 @@ public class ReleaseOperations {
for (ModuleIteration module : iteration) {
Changelog changelog = trackers.getPluginFor(module.getProject()).getChangelogFor(module);
File file = workspace.getFile("src/main/resources/changelog.txt", module.getProject());
StringBuilder builder = new StringBuilder();
final Changelog changelog = trackers.getPluginFor(module.getProject()).getChangelogFor(module);
try (Scanner scanner = new Scanner(file)) {
for (String location : CHANGELOG_LOCATIONS) {
// Copy headline
builder.append(scanner.nextLine()).append("\n");
builder.append(scanner.nextLine()).append("\n");
boolean processed = workspace.processFile(location, module.getProject(), new LineCallback() {
// Add new changelog
builder.append(changelog.toString());
@Override
public String doWith(String line, long number) {
// Append existing
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine()).append("\n");
if (line.startsWith("=")) {
StringBuilder builder = new StringBuilder();
builder.append(line).append("\n\n");
builder.append(changelog.toString());
return builder.toString();
} else {
return line;
}
}
});
if (processed) {
logger.log(module.getProject(), "Updated changelog %s.", location);
}
}
}
}
Files.write(builder, file, Charset.forName("UTF-8"));
public void updateResources(TrainIteration iteration) throws Exception {
for (final ModuleIteration module : iteration) {
workspace.processFile("src/main/resources/notice.txt", module.getProject(), new LineCallback() {
@Override
public String doWith(String line, long number) {
if (number != 0) {
return line;
}
return module.toString();
}
});
logger.log(module, "Updated notice.txt.");
}
}
}

View File

@@ -62,6 +62,10 @@ public class Iteration {
return name.startsWith("SR");
}
public boolean isNext(Iteration iteration) {
return next.equals(iteration);
}
public int getBugfixValue() {
return name.startsWith("SR") ? Integer.parseInt(name.substring(2)) : 0;
}

View File

@@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@@ -53,4 +55,16 @@ public class Project {
public String getFullName() {
return "Spring Data ".concat(name);
}
/**
* Returns whether the current project depends on the given one.
*
* @param project must not be {@literal null}.
* @return
*/
public boolean dependsOn(Project project) {
Assert.notNull(project, "Project must not be null!");
return dependencies.contains(project);
}
}

View File

@@ -139,6 +139,10 @@ public class Train implements Iterable<Module> {
return ArtifactVersion.from(new ModuleIteration(module, iteration, this));
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
@@ -157,7 +161,7 @@ public class Train implements Iterable<Module> {
*/
@EqualsAndHashCode
@ToString
private static class Iterations implements Iterable<Iteration> {
public static class Iterations implements Iterable<Iteration> {
public static Iterations DEFAULT = new Iterations(M1, RC1, GA, SR1, SR2, SR3, SR4);
@@ -191,6 +195,17 @@ public class Train implements Iterable<Module> {
return null;
}
Iteration getPreviousIteration(Iteration iteration) {
for (Iteration candidate : iterations) {
if (candidate.isNext(iteration)) {
return candidate;
}
}
throw new IllegalArgumentException(String.format("Could not find previous iteration for %s!", iteration));
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()

View File

@@ -53,6 +53,12 @@ public class TrainIteration implements Iterable<ModuleIteration> {
return train.getModuleIterations(iteration, exclusions);
}
public ModuleIteration getPreviousIteration(ModuleIteration module) {
Iteration previousIteration = train.getIterations().getPreviousIteration(iteration);
return train.getModuleIteration(previousIteration, module.getProject().getName());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()