Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JENKINS-43507] Should be first round complete for scm-api changes
  • Loading branch information
stephenc committed May 4, 2017
1 parent de0ad6a commit 4ff2c17
Show file tree
Hide file tree
Showing 19 changed files with 638 additions and 83 deletions.
Expand Up @@ -259,7 +259,7 @@ public final C withDecorators(@NonNull Collection<? extends SCMSourceDecorator<?
* Creates a new {@link SCMNavigatorRequest}.
*
* @param navigator the {@link SCMNavigator}.
* @param observer the {@Link SCMSourceObserver}.
* @param observer the {@link SCMSourceObserver}.
* @return the {@link R}
*/
@NonNull
Expand Down
50 changes: 48 additions & 2 deletions src/main/java/jenkins/scm/api/trait/SCMSourceDecorator.java
Expand Up @@ -30,23 +30,54 @@
import jenkins.scm.api.SCMSource;
import org.jvnet.tiger_types.Types;

/**
* A contextual decorator of {@link SCMSourceBuilder} instances that can be used by a {@link SCMNavigatorTrait} for
* example to apply {@link SCMSourceTrait}s to a subset of projects.
*
* @param <B> the type of {@link SCMSourceBuilder}.
* @param <S> the type of {@link SCMSource}.
*/
public abstract class SCMSourceDecorator<B extends SCMSourceBuilder<B, S>, S extends SCMSource> {

/**
* A map of inferred {@link SCMSourceBuilder} classes to reduce the instantiation time cost of inferring the
* builder class from the type parameters of the implementation class.
*/
private static final WeakHashMap<Class<? extends SCMSourceDecorator>, Class<? extends SCMSourceBuilder>> builders
= new WeakHashMap<Class<? extends SCMSourceDecorator>, Class<? extends SCMSourceBuilder>>();

/**
* The {@link B} class.
*/
private final Class<B> builderClass;

/**
* Infers the type of the corresponding {@link SCMSourceBuilder} from the type parameters of the implementation
* class. If the inference fails use {@link #SCMSourceDecorator(Class)}.
*/
protected SCMSourceDecorator() {
builderClass = SCMSourceDecorator.<B,S>lookupBuilderClass(getClass());
builderClass = SCMSourceDecorator.<B, S>inferBuilderClass(getClass());
}

/**
* Bypasses {@link SCMSourceBuilder} type inference and specifies the type explicitly.
*
* @param builderClass the specialization of {@link SCMSourceBuilder} that this decorator applies to.
*/
protected SCMSourceDecorator(Class<B> builderClass) {
this.builderClass = builderClass;
}

/**
* Infers the type of builder class from the type of {@link SCMSourceDecorator}.
*
* @param clazz the type of {@link SCMSourceDecorator}
* @param <B> coerces types by cheating (but this cheating is OK as we are deriving from type parameters)
* @param <S> coerces types by cheating (but this cheating is OK as we are deriving from type parameters)
* @return the specialization of {@link SCMSourceBuilder} that the specified {@link SCMSourceDecorator} applies to.
*/
@SuppressWarnings("unchecked")
private static <B extends SCMSourceBuilder<B, S>, S extends SCMSource> Class<B> lookupBuilderClass(
private static <B extends SCMSourceBuilder<B, S>, S extends SCMSource> Class<B> inferBuilderClass(
Class<? extends SCMSourceDecorator> clazz) {
synchronized (builders) {
Class result = builders.get(clazz);
Expand All @@ -71,11 +102,26 @@ private static <B extends SCMSourceBuilder<B, S>, S extends SCMSource> Class<B>
}
}

/**
* Applies this decorator to the specified {@link SCMSourceBuilder} for the supplied project name.
* If this {@link SCMSourceDecorator} is not specialized for the type of {@link SCMSourceBuilder} then this will
* be a no-op.
*
* @param builder the {@link SCMSourceBuilder}.
* @param projectName the project name.
*/
public final void applyTo(SCMSourceBuilder<?, ?> builder, String projectName) {
if (builderClass.isInstance(builder)) {
// we guard the SPI so that implementations can avoid casting while the API is generic so that
// consumers do not need to care about checking individual decorators for "fit"
decorate(builderClass.cast(builder), projectName);
}
}

/**
* SPI: decorate the supplied builder for creation of the named project.
* @param builder the builder to decorate.
* @param projectName the project name.
*/
protected abstract void decorate(B builder, String projectName);
}
140 changes: 101 additions & 39 deletions src/main/java/jenkins/scm/api/trait/SCMSourceRequest.java
Expand Up @@ -118,7 +118,7 @@ public abstract class SCMSourceRequest implements Closeable {
/**
* Constructor.
*
* @param source the source.
* @param source the source.
* @param context the context.
* @param listener the (optional) {@link TaskListener}.
*/
Expand All @@ -136,6 +136,22 @@ protected SCMSourceRequest(@NonNull SCMSource source, @NonNull SCMSourceContext<
this.listener = defaultListener(listener);
}

/**
* Records a processing result to the {@linkplain Witness}es.
*
* @param head the {@link SCMHead}.
* @param revision the {@link SCMRevision}.
* @param isMatch {@code true} if the head:revision pair was sent to the {@link #observer}.
* @param witnesses the {@link Witness} instances to notify.
*/
@SuppressWarnings("unchecked")
private static void record(@NonNull SCMHead head, SCMRevision revision, boolean isMatch,
@NonNull Witness... witnesses) {
for (Witness witness : witnesses) {
witness.record(head, revision, isMatch);
}
}

/**
* Turns a possibly {@code null} {@link TaskListener} reference into a guaranteed non-null reference.
*
Expand All @@ -156,22 +172,6 @@ private TaskListener defaultListener(@CheckForNull TaskListener listener) {
return listener;
}

/**
* Records a processing result to the {@linkplain Witness}es.
*
* @param head the {@link SCMHead}.
* @param revision the {@link SCMRevision}.
* @param isMatch {@code true} if the head:revision pair was sent to the {@link #observer}.
* @param witnesses the {@link Witness} instances to notify.
*/
@SuppressWarnings("unchecked")
private static void record(@NonNull SCMHead head, SCMRevision revision, boolean isMatch,
@NonNull Witness... witnesses) {
for (Witness witness : witnesses) {
witness.record(head, revision, isMatch);
}
}

/**
* Tests if the {@link SCMHead} is excluded from the request.
*
Expand Down Expand Up @@ -244,19 +244,22 @@ public final List<SCMSourceCriteria> getCriteria() {
* @throws InterruptedException if the processing was interrupted.
*/
public final <H extends SCMHead, R extends SCMRevision> boolean process(final @NonNull H head,
final R revision,
final @NonNull R revision,
@NonNull ProbeLambda<H, R> probeFactory,
@NonNull Witness... witnesses)
throws IOException, InterruptedException {
return process(head, new IntermediateLambda<R>() {
@Nullable
@Override
public R create() throws IOException, InterruptedException {
return revision;
}
}, probeFactory, new LazyRevisionLambda<H, SCMRevision, R>() {
@NonNull
@Override
public SCMRevision create(H head, R intermediate) throws IOException, InterruptedException {
return intermediate;
public SCMRevision create(@NonNull H head, @Nullable R ignored)
throws IOException, InterruptedException {
return revision;
}
}, witnesses);
}
Expand All @@ -283,14 +286,18 @@ public final <H extends SCMHead, R extends SCMRevision> boolean process(final @N
@NonNull Witness... witnesses)
throws IOException, InterruptedException {
return process(head, new IntermediateLambda<R>() {
@NonNull
@Override
public R create() throws IOException, InterruptedException {
return revisionFactory.create(head);
}
}, probeFactory, new LazyRevisionLambda<H, SCMRevision, R>() {
@NonNull
@Override
public SCMRevision create(H head, R intermediate) throws IOException, InterruptedException {
return intermediate;
public SCMRevision create(@NonNull H head, @Nullable R revision)
throws IOException, InterruptedException {
assert revision != null;
return revision;
}
}, witnesses);
}
Expand Down Expand Up @@ -335,7 +342,7 @@ public final <H extends SCMHead, I, R extends SCMRevision> boolean process(@NonN
try {
for (SCMSourceCriteria c : criteria) {
if (!c.isHead(probe, listener)) {
record((H) head, null, false, witnesses);
record(head, null, false, witnesses);
// not a match against criteria
return !observer.isObserving();
}
Expand Down Expand Up @@ -423,6 +430,14 @@ public void close() throws IOException {
* @param <R> the type of {@link SCMRevision}.
*/
public interface RevisionLambda<H extends SCMHead, R extends SCMRevision> {
/**
* Creates a {@link SCMRevision} for the specified {@link SCMHead}.
*
* @param head the {@link SCMHead}.
* @return the {@link SCMRevision}.
* @throws IOException if there is an I/O error.
* @throws InterruptedException if the operation was interrupted.
*/
@NonNull
R create(@NonNull H head) throws IOException, InterruptedException;
}
Expand All @@ -439,21 +454,23 @@ public interface RevisionLambda<H extends SCMHead, R extends SCMRevision> {
* @see IntermediateLambda
*/
public interface ProbeLambda<H extends SCMHead, I> {
/**
* Creates a {@link SCMSourceCriteria.Probe} (ideally a {@link SCMProbe}) for the specified {@link SCMHead}
* and {@link SCMRevision} produced by {@link RevisionLambda} or intermediate produced by
* {@link IntermediateLambda}
*
* @param head the {@link SCMHead}.
* @param revisionInfo depending on the type of {@link I} this is either a {@link SCMRevision} produced by a
* {@link RevisionLambda} or an intermediate produced by a {@link IntermediateLambda}.
* It could also be {@code null} if the implementation has captured sufficient information
* to create the {@link SCMSourceCriteria.Probe}.
* @return ideally a {@link SCMProbe}.
* @throws IOException if there is an I/O error.
* @throws InterruptedException if the operation was interrupted.
*/
@NonNull
SCMSourceCriteria.Probe create(@NonNull H head, @Nullable I revision) throws IOException, InterruptedException;
}

/**
* A lambda that will create the {@link SCMRevision} instance for a specific {@link SCMHead} using the intermediate
* value produced by a {@link IntermediateLambda}.
* @param <H> the type of {@link SCMHead}
* @param <R> the type of {@link SCMRevision}.
* @param <I> the type of intermediate value produced by the {@link IntermediateLambda}.
* @see IntermediateLambda
*/
public interface LazyRevisionLambda<H extends SCMHead, R extends SCMRevision, I> {
@NonNull
R create(@NonNull H head, @Nullable I intermediate) throws IOException, InterruptedException;
SCMSourceCriteria.Probe create(@NonNull H head, @Nullable I revisionInfo)
throws IOException, InterruptedException;
}

/**
Expand All @@ -471,21 +488,66 @@ public interface LazyRevisionLambda<H extends SCMHead, R extends SCMRevision, I>
* the computation of the merge revision can be avoided completely.
*
* @param <I> the type of intermediate value or {@link Void} if no intermediate is required.
* @see LazyRevisionLambda
* @see ProbeLambda
* @see LazyRevisionLambda
* @see ProbeLambda
*/
public interface IntermediateLambda<I> {

/**
* Creates an intermediate representation of the {@link SCMRevision} that can be used by {@link ProbeLambda}
* and {@link LazyRevisionLambda} to create the {@link SCMSourceCriteria.Probe} and {@link SCMRevision}
* respectively. This lambda is used where the creation of the {@link SCMRevision} may involve a
* <strong>more time costly</strong> operation that the creation of the {@link SCMSourceCriteria.Probe}.
*
* @return the intermediate (or {@code null} if the implementer of {@link ProbeLambda} and
* {@link LazyRevisionLambda} is expecting {@code null} under defined conditions).
* @throws IOException if there is an I/O error.
* @throws InterruptedException if the operation was interrupted.
*/
@Nullable
I create() throws IOException, InterruptedException;
}

/**
* A lambda that will create the {@link SCMRevision} instance for a specific {@link SCMHead} using the intermediate
* value produced by a {@link IntermediateLambda}.
*
* @param <H> the type of {@link SCMHead}
* @param <R> the type of {@link SCMRevision}.
* @param <I> the type of intermediate value produced by the {@link IntermediateLambda}.
* @see IntermediateLambda
*/
public interface LazyRevisionLambda<H extends SCMHead, R extends SCMRevision, I> {
/**
* Creates a {@link SCMRevision} for the specified {@link SCMHead} using the supplied intermediate previously
* generated by an {@link IntermediateLambda}.
*
* @param head the {@link SCMHead}.
* @param intermediate the intermediate (may be {@code null} if no {@link IntermediateLambda} was provided
* or if the {@link IntermediateLambda} can return {@code null}.
* @return the {@link SCMRevision}.
* @throws IOException if there is an I/O error.
* @throws InterruptedException if the operation was interrupted.
*/
@NonNull
R create(@NonNull H head, @Nullable I intermediate) throws IOException, InterruptedException;
}

/**
* Callback lambda to track the results of
* {@link #process(SCMHead, IntermediateLambda, ProbeLambda, LazyRevisionLambda, Witness[])}
*
* @param <H> the type of {@link SCMHead}
* @param <R> the type of {@link SCMRevision}
*/
public interface Witness<H extends SCMHead, R extends SCMRevision> {
/**
* Records the result of a {@link SCMHead}.
*
* @param head the {@link SCMHead}.
* @param revision (optional) the resolved {@link SCMRevision}.
* @param isMatch {@code true} if the head matched.
*/
void record(@NonNull H head, @CheckForNull R revision, boolean isMatch);
}
}
29 changes: 27 additions & 2 deletions src/main/java/jenkins/scm/api/trait/SCMSourceTrait.java
Expand Up @@ -58,7 +58,8 @@ public final void applyToContext(SCMSourceContext<?, ?> context) {
* SPI: Override this method to decorate a {@link SCMSourceContext}. You can assume that your
* {@link SCMSourceTraitDescriptor#isApplicableToContext(Class)} is {@code true} within this method.
*
* @param context the context (invariant: {@link SCMSourceTraitDescriptor#isApplicableToContext(Class)} is {@code true})
* @param context the context (invariant: {@link SCMSourceTraitDescriptor#isApplicableToContext(Class)} is {@code
* true})
* @param <B> generic type parameter to ensure type information available.
* @param <R> generic type parameter to ensure type information available.
*/
Expand Down Expand Up @@ -131,21 +132,45 @@ protected boolean includeCategory(@NonNull SCMHeadCategory category) {
return false;
}

/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public SCMSourceTraitDescriptor getDescriptor() {
return (SCMSourceTraitDescriptor) super.getDescriptor();
}

/**
* Returns all the {@link SCMSourceTraitDescriptor} instances.
*
* @return all the {@link SCMSourceTraitDescriptor} instances.
*/
public static DescriptorExtensionList<SCMSourceTrait, SCMSourceTraitDescriptor> all() {
return SCMTrait.all(SCMSourceTrait.class);
}

/**
* Returns the subset of {@link SCMSourceTraitDescriptor} instances that are applicable to the specified types
* of {@link SCMSourceContext} and {@link SCMSourceBuilder}.
*
* @param contextClass (optional) type of {@link SCMSourceContext}.
* @param builderClass (optional) type of {@link SCMBuilder}.
* @return the list of matching {@link SCMSourceTraitDescriptor} instances.
*/
public static List<SCMSourceTraitDescriptor> _for(Class<? extends SCMSourceContext> contextClass,
Class<? extends SCMBuilder> builderClass) {
return _for(null, contextClass, builderClass);
}

/**
* Returns the subset of {@link SCMSourceTraitDescriptor} instances that are applicable to the specified
* {@link SCMSourceDescriptor} and specified types of {@link SCMNavigatorContext} and {@link SCMSourceBuilder}.
*
* @param scmSource (optional) {@link SCMSourceDescriptor}.
* @param contextClass (optional) type of {@link SCMSourceContext}.
* @param builderClass (optional) type of {@link SCMBuilder}.
* @return the list of matching {@link SCMSourceTraitDescriptor} instances.
*/
public static List<SCMSourceTraitDescriptor> _for(@CheckForNull SCMSourceDescriptor scmSource,
@CheckForNull Class<? extends SCMSourceContext> contextClass,
@CheckForNull Class<? extends SCMBuilder> builderClass) {
Expand Down

0 comments on commit 4ff2c17

Please sign in to comment.