Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #36 from stephenc/jenkins-43507
[JENKINS-43507] Allow SCMSource and SCMNavigator subtypes to share common traits
  • Loading branch information
stephenc committed Jun 14, 2017
2 parents 46581df + 0b6a184 commit cec5622
Show file tree
Hide file tree
Showing 74 changed files with 6,447 additions and 316 deletions.
15 changes: 8 additions & 7 deletions pom.xml
Expand Up @@ -29,7 +29,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.25</version>
<version>2.27</version>
<relativePath />
</parent>

Expand Down Expand Up @@ -102,19 +102,20 @@
<version>1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<version>2.5.3</version> <!-- 2.4.x does not work since it lacks https://github.com/jenkinsci/git-plugin/pull/402 -->
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate-taglib-interface</goal>
</goals>
</execution>
</executions>
<configuration>
<compatibleSinceVersion>2.0.0</compatibleSinceVersion>
</configuration>
Expand Down
47 changes: 46 additions & 1 deletion src/main/java/jenkins/scm/api/SCMHeadObserver.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright (c) 2011-2013, CloudBees, Inc., Stephen Connolly.
* Copyright (c) 2011-2017, CloudBees, Inc., Stephen Connolly.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,6 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.scm.api;

import edu.umd.cs.findbugs.annotations.CheckForNull;
Expand Down Expand Up @@ -158,6 +159,17 @@ public static Any any() {
return new Any();
}

/**
* Creates an observer that selects the first revision it finds. Also useful for quick checks of non-empty.
*
* @return an observer that selects the first revision of a any head.
* @since 2.2.0
*/
@NonNull
public static None none() {
return None.INSTANCE;
}

/**
* An observer that wraps multiple observers and keeps observing as long as one of the wrapped observers wants to.
*/
Expand Down Expand Up @@ -545,6 +557,39 @@ public boolean isObserving() {

}

/**
* An observer that is already finished.
*
* @since 2.2.0
*/
public static final class None extends SCMHeadObserver {
/**
* Singleton.
*/
public static final None INSTANCE = new None();

/**
* Constructor.
*/
private None() {
}

/**
* {@inheritDoc}
*/
@Override
public void observe(@NonNull SCMHead head, @NonNull SCMRevision revision) {
}

/**
* {@inheritDoc}
*/
@Override
public boolean isObserving() {
return false;
}
}

/**
* Base class for an {@link SCMHeadObserver} that wraps another {@link SCMHeadObserver} while allowing access to the
* original observer via {@link #unwrap()}.
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/jenkins/scm/api/SCMSourceObserver.java
Expand Up @@ -81,9 +81,12 @@ public Set<String> getIncludes() {
* {@link Item#getName}
* @return a secondary callback to customize the project, on which you must call {@link ProjectObserver#complete}
* @throws IllegalArgumentException if this {@code projectName} has already been encountered
* @throws IOException if observing this {@code projectName} could not be completed due to an {@link IOException}.
* @throws InterruptedException if observing this {@code projectName} was interrupted.
*/
@NonNull
public abstract ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException;
public abstract ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException, IOException,
InterruptedException;

/**
* Adds extra metadata about the overall organization.
Expand Down Expand Up @@ -212,7 +215,8 @@ public Set<String> getIncludes() {
*/
@Override
@NonNull
public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException {
public ProjectObserver observe(@NonNull String projectName)
throws IllegalArgumentException, IOException, InterruptedException {
return delegate.observe(projectName);
}

Expand Down Expand Up @@ -280,7 +284,8 @@ public Set<String> getIncludes() {
*/
@NonNull
@Override
public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException {
public ProjectObserver observe(@NonNull String projectName)
throws IllegalArgumentException, IOException, InterruptedException {
if (remaining.remove(projectName)) {
return super.observe(projectName);
} else {
Expand Down
Expand Up @@ -111,7 +111,10 @@ public final boolean equals(Object o) {
if (!equivalent(that)) {
return false;
}
return !isMerge() || target.equals(that.target);
if (isMerge() && that.isMerge()) {
return target.equals(that.target);
}
return isMerge() == that.isMerge();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/jenkins/scm/api/mixin/SCMHeadMixin.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright (c) 2016 CloudBees, Inc.
* Copyright (c) 2016-2017 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down
191 changes: 191 additions & 0 deletions src/main/java/jenkins/scm/api/trait/SCMBuilder.java
@@ -0,0 +1,191 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.scm.api.trait;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.scm.SCM;
import java.util.Arrays;
import java.util.Collection;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;

/**
* Builder for a {@link SCM} instance. Typically instantiated in {@link SCMSource#build(SCMHead, SCMRevision)} or
* {@link SCMSource#build(SCMHead)} and then decorated by {@link SCMSourceTrait#applyToBuilder(SCMBuilder)} before
* calling {@link #build()} to generate the return value. Conventions:
* <ul>
* <li>The builder is not designed to be shared by multiple threads.</li>
* <li>All methods should be either {@code final} or {@code abstract} unless there is a documented reason for
* allowing overrides</li>
* <li>All "setter" methods will return {@link B} and be called "withXxx"</li>
* <li>All "getter" methods will be called "xxx()". Callers should not assume that the returned value is resistant
* from concurrent changes. Implementations should ensure that the returned value is immutable by the caller.
* In other words, it is intentional for implementations to reduce intermediate allocations by
* {@code return Collections.unmodifiableList(theList);} rather than the concurrency safe
* {@code return Collections.unmodifiableList(new ArrayList<>(theList));}
* </li>
* </ul>
*
* @param <B> the type of {@link SCMBuilder} so that subclasses can chain correctly in their {@link #withHead(SCMHead)}
* etc methods.
* @param <S> the type of {@link SCM} returned by {@link #build()}
*/
public abstract class SCMBuilder<B extends SCMBuilder<B,S>,S extends SCM> {

/**
* The base class of {@link SCM} that will be produced by the {@link SCMBuilder}.
*/
@NonNull
private final Class<S> clazz;
/**
* The {@link SCMHead} to produce the {@link SCM} for.
*/
@NonNull
private SCMHead head;
/**
* The {@link SCMRevision} to produce the {@link SCM} for or {@code null} to produce the {@link SCM} for the head
* revision.
*/
@CheckForNull
private SCMRevision revision;

/**
* Constructor.
*
* @param clazz The base class of {@link SCM} that will be produced by the {@link SCMBuilder}.
* @param head The {@link SCMHead} to produce the {@link SCM} for.
* @param revision The {@link SCMRevision} to produce the {@link SCM} for or {@code null} to produce the
* {@link SCM} for the head revision.
*/
public SCMBuilder(Class<S> clazz, @NonNull SCMHead head, @CheckForNull SCMRevision revision) {
this.clazz = clazz;
this.head = head;
this.revision = revision;
}

/**
* Returns the {@link SCMHead} to produce the {@link SCM} for.
*
* @return the {@link SCMHead} to produce the {@link SCM} for.
*/
@NonNull
public final SCMHead head() {
return head;
}

/**
* Returns the {@link SCMRevision} to produce the {@link SCM} for or {@code null} to produce the {@link SCM} for
* the head revision.
*
* @return the {@link SCMRevision} to produce the {@link SCM} for or {@code null} to produce the {@link SCM} for
* the head revision.
*/
@CheckForNull
public final SCMRevision revision() {
return revision;
}

/**
* Returns the base class of {@link SCM} that will be produced by the {@link SCMBuilder}.
*
* @return the base class of {@link SCM} that will be produced by the {@link SCMBuilder}.
*/
@NonNull
public final Class<S> scmClass() {
return clazz;
}

/**
* Replace the {@link #head()} with a new {@link SCMHead}.
*
* @param head the {@link SCMHead} to produce the {@link SCM} for.
* @return {@code this} for method chaining.
*/
@NonNull
public final B withHead(@NonNull SCMHead head) {
this.head = head;
return (B) this;
}

/**
* Replace the {@link #revision()} with a new {@link SCMRevision}
*
* @param revision the {@link SCMRevision} to produce the {@link SCM} for or {@code null} to produce the
* {@link SCM} for the head revision.
* @return {@code this} for method chaining.
*/
@NonNull
public final B withRevision(@CheckForNull SCMRevision revision) {
this.revision = revision;
return (B) this;
}

/**
* Apply the {@link SCMSourceTrait} to this {@link SCMBuilder}.
* @param trait the {@link SCMSourceTrait}.
* @return {@code this} for method chaining.
*/
@SuppressWarnings("unchecked")
@NonNull
public final B withTrait(@NonNull SCMSourceTrait trait) {
trait.applyToBuilder(this);
return (B) this;
}

/**
* Apply the {@link SCMSourceTrait} instances to this {@link SCMBuilder}.
*
* @param traits the {@link SCMSourceTrait} instances.
* @return {@code this} for method chaining.
*/
@NonNull
public final B withTraits(@NonNull SCMSourceTrait... traits) {
return withTraits(Arrays.asList(traits));
}

/**
* Apply the {@link SCMSourceTrait} instances to this {@link SCMBuilder}.
*
* @param traits the {@link SCMSourceTrait} instances.
* @return {@code this} for method chaining.
*/
@SuppressWarnings("unchecked")
@NonNull
public final B withTraits(@NonNull Collection<SCMSourceTrait> traits) {
for (SCMSourceTrait trait : traits) {
withTrait(trait);
}
return (B) this;
}

/**
* Instantiates the {@link SCM}.
* @return the {@link S} instance
*/
@NonNull
public abstract S build();
}

0 comments on commit cec5622

Please sign in to comment.