copy) {
XmlFile cf = configFile();
- LOGGER.log(FINE, "saving {0} to {1}", new Object[] {copy, cf});
+ LOGGER.log(Level.FINE, "saving {0} to {1}", new Object[] {copy, cf});
if (cf == null) {
return; // oh well
}
try {
cf.write(copy);
} catch (IOException x) {
- LOGGER.log(WARNING, null, x);
+ LOGGER.log(Level.WARNING, null, x);
}
}
@@ -164,38 +171,34 @@ public static FlowExecutionList get() {
return l;
}
+ /**
+ * Returns true if all executions that were present in this {@link FlowExecutionList} have been loaded.
+ *
+ * This takes place slightly after {@link InitMilestone#COMPLETED} is reached during Jenkins startup.
+ *
+ *
Useful to avoid resuming Pipelines in contexts that may lead to deadlock.
+ *
+ *
It is not guaranteed that {@link FlowExecution#afterStepExecutionsResumed} has been called at this point.
+ */
+ @Restricted(Beta.class)
+ public boolean isResumptionComplete() {
+ return resumptionComplete;
+ }
+
/**
* When Jenkins starts up and everything is loaded, be sure to proactively resurrect
* all the ongoing {@link FlowExecution}s so that they start running again.
*/
@Extension
public static class ItemListenerImpl extends ItemListener {
- @Inject
- FlowExecutionList list;
-
@Override
public void onLoaded() {
+ FlowExecutionList list = FlowExecutionList.get();
for (final FlowExecution e : list) {
- LOGGER.log(FINE, "Eager loading {0}", e);
- Futures.addCallback(e.getCurrentExecutions(false), new FutureCallback>() {
- @Override
- public void onSuccess(List result) {
- LOGGER.log(FINE, "Will resume {0}", result);
- for (StepExecution se : result) {
- se.onResume();
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CancellationException) {
- LOGGER.log(Level.FINE, "Cancelled load of " + e, t);
- } else {
- LOGGER.log(WARNING, "Failed to load " + e, t);
- }
- }
- });
+ // The call to FlowExecutionOwner.get in the implementation of iterator() is sufficent to load the Pipeline.
+ LOGGER.log(Level.FINE, "Eagerly loaded {0}", e);
}
+ list.resumptionComplete = true;
}
}
@@ -204,19 +207,16 @@ public void onFailure(Throwable t) {
*/
@Extension
public static class StepExecutionIteratorImpl extends StepExecutionIterator {
- @Inject
- FlowExecutionList list;
-
@Override
public ListenableFuture> apply(final Function f) {
List> all = new ArrayList<>();
- for (FlowExecution e : list) {
+ for (FlowExecution e : FlowExecutionList.get()) {
ListenableFuture> execs = e.getCurrentExecutions(false);
all.add(execs);
Futures.addCallback(execs,new FutureCallback>() {
@Override
- public void onSuccess(List result) {
+ public void onSuccess(@NonNull List result) {
for (StepExecution e : result) {
try {
f.apply(e);
@@ -227,10 +227,10 @@ public void onSuccess(List result) {
}
@Override
- public void onFailure(Throwable t) {
+ public void onFailure(@NonNull Throwable t) {
LOGGER.log(Level.WARNING, null, t);
}
- });
+ }, MoreExecutors.directExecutor());
}
return Futures.allAsList(all);
@@ -254,4 +254,140 @@ public void onFailure(Throwable t) {
executor.awaitTermination(1, TimeUnit.MINUTES);
}
+ /**
+ * Whenever a Pipeline resumes, resume all incomplete steps in its {@link FlowExecution}.
+ *
+ * Called by {@code WorkflowRun.onLoad}, so guaranteed to run if a Pipeline resumes
+ * regardless of its presence in {@link FlowExecutionList}.
+ */
+ @Extension
+ public static class ResumeStepExecutionListener extends FlowExecutionListener {
+ @Override
+ public void onResumed(@NonNull FlowExecution e) {
+ Futures.addCallback(e.getCurrentExecutions(false), new FutureCallback>() {
+ @Override
+ public void onSuccess(@NonNull List result) {
+ if (e.isComplete()) {
+ // WorkflowRun.onLoad will not fireResumed if the execution was already complete when loaded,
+ // and CpsFlowExecution should not then complete until afterStepExecutionsResumed, so this is defensive.
+ return;
+ }
+ FlowExecutionList list = FlowExecutionList.get();
+ FlowExecutionOwner owner = e.getOwner();
+ if (!list.runningTasks.contains(owner)) {
+ LOGGER.log(Level.WARNING, "Resuming {0}, which is missing from FlowExecutionList ({1}), so registering it now.", new Object[] {owner, list.runningTasks.getView()});
+ list.register(owner);
+ }
+ LOGGER.log(Level.FINE, "Will resume {0}", result);
+ new ParallelResumer(result, e::afterStepExecutionsResumed).run();
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ if (t instanceof CancellationException) {
+ LOGGER.log(Level.FINE, "Cancelled load of " + e, t);
+ } else {
+ LOGGER.log(Level.WARNING, "Failed to load " + e, t);
+ }
+ e.afterStepExecutionsResumed();
+ }
+
+ }, MoreExecutors.directExecutor());
+ }
+ }
+
+ /** Calls {@link StepExecution#onResume} for each step in a running build.
+ * Does so in parallel, but always completing enclosing blocks before the enclosed step.
+ * A simplified version of https://stackoverflow.com/a/67449067/12916, since this should be a tree not a general DAG.
+ */
+ private static final class ParallelResumer {
+
+ private final Runnable onCompletion;
+ /** Step nodes mapped to the step execution. Entries removed when they are ready to be resumed. */
+ private final Map nodes = new HashMap<>();
+ /** Step nodes currently being resumed. Removed after resumption completes. */
+ private final Set processing = new HashSet<>();
+ /** Step nodes mapped to the nearest enclosing step node (no entry if at root). */
+ private final Map enclosing = new HashMap<>();
+
+ ParallelResumer(Collection executions, Runnable onCompletion) {
+ this.onCompletion = onCompletion;
+ // First look up positions in the flow graph, so that we can compute dependencies:
+ for (StepExecution se : executions) {
+ try {
+ FlowNode n = se.getContext().get(FlowNode.class);
+ if (n != null) {
+ nodes.put(n, se);
+ } else {
+ LOGGER.warning(() -> "Could not find FlowNode for " + se + " so it will not be resumed");
+ }
+ } catch (IOException | InterruptedException x) {
+ LOGGER.log(Level.WARNING, "Could not look up FlowNode for " + se + " so it will not be resumed", x);
+ }
+ }
+ for (FlowNode n : nodes.keySet()) {
+ LinearBlockHoppingScanner scanner = new LinearBlockHoppingScanner();
+ scanner.setup(n);
+ for (FlowNode parent : scanner) {
+ if (parent != n && nodes.containsKey(parent)) {
+ enclosing.put(n, parent);
+ break;
+ }
+ }
+ }
+ }
+
+ synchronized void run() {
+ if (Jenkins.get().isTerminating()) {
+ LOGGER.fine("Skipping step resumption during shutdown");
+ return;
+ }
+ if (Jenkins.get().getInitLevel() != InitMilestone.COMPLETED || Jenkins.get().isQuietingDown()) {
+ LOGGER.fine("Waiting to resume step until Jenkins completes startup and is not in quiet mode");
+ Timer.get().schedule(this::run, 100, TimeUnit.MILLISECONDS);
+ return;
+ }
+ LOGGER.fine(() -> "Checking status with nodes=" + nodes + " enclosing=" + enclosing + " processing=" + processing);
+ if (nodes.isEmpty()) {
+ if (processing.isEmpty()) {
+ LOGGER.fine("Done");
+ onCompletion.run();
+ }
+ return;
+ }
+ Map ready = new HashMap<>();
+ for (Map.Entry entry : nodes.entrySet()) {
+ FlowNode n = entry.getKey();
+ FlowNode parent = enclosing.get(n);
+ if (parent == null || !nodes.containsKey(parent)) {
+ ready.put(n, entry.getValue());
+ }
+ }
+ LOGGER.fine(() -> "Ready to resume: " + ready);
+ nodes.keySet().removeAll(ready.keySet());
+ for (Map.Entry entry : ready.entrySet()) {
+ FlowNode n = entry.getKey();
+ StepExecution exec = entry.getValue();
+ processing.add(n);
+ // Strictly speaking threadPoolForRemoting should be used for agent communications.
+ // In practice the only onResume impl known to block is in ExecutorStepExecution.
+ // Avoid jenkins.util.Timer since it is capped at 10 threads and should not be used for long tasks.
+ Computer.threadPoolForRemoting.submit(() -> {
+ LOGGER.fine(() -> "About to resume " + n + " ~ " + exec);
+ try {
+ exec.onResume();
+ } catch (Throwable x) {
+ exec.getContext().onFailure(x);
+ }
+ LOGGER.fine(() -> "Finished resuming " + n + " ~ " + exec);
+ synchronized (ParallelResumer.this) {
+ processing.remove(n);
+ run();
+ }
+ });
+ }
+ }
+
+ }
+
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListener.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListener.java
index 35afcae0..bfdc5035 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListener.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListener.java
@@ -5,7 +5,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Listens for significant status updates for a {@link FlowExecution}, such as started running or completed.
@@ -24,7 +24,7 @@ public abstract class FlowExecutionListener implements ExtensionPoint {
*
* @param execution The {@link FlowExecution} that has been created.
*/
- public void onCreated(@Nonnull FlowExecution execution) {
+ public void onCreated(@NonNull FlowExecution execution) {
}
/**
@@ -34,7 +34,7 @@ public void onCreated(@Nonnull FlowExecution execution) {
*
* @param execution The {@link FlowExecution} that has started running.
*/
- public void onRunning(@Nonnull FlowExecution execution) {
+ public void onRunning(@NonNull FlowExecution execution) {
}
/**
@@ -42,7 +42,7 @@ public void onRunning(@Nonnull FlowExecution execution) {
*
* @param execution The {@link FlowExecution} that has resumed.
*/
- public void onResumed(@Nonnull FlowExecution execution) {
+ public void onResumed(@NonNull FlowExecution execution) {
}
/**
@@ -55,13 +55,14 @@ public void onResumed(@Nonnull FlowExecution execution) {
*
* @param execution The {@link FlowExecution} that has completed.
*/
- public void onCompleted(@Nonnull FlowExecution execution) {
+ public void onCompleted(@NonNull FlowExecution execution) {
}
/**
* Fires the {@link #onCreated(FlowExecution)} event.
*/
- public static void fireCreated(@Nonnull FlowExecution execution) {
+ public static void fireCreated(@NonNull FlowExecution execution) {
+ // TODO Jenkins 2.325+ use Listeners.notify
for (FlowExecutionListener listener : ExtensionList.lookup(FlowExecutionListener.class)) {
listener.onCreated(execution);
}
@@ -70,7 +71,7 @@ public static void fireCreated(@Nonnull FlowExecution execution) {
/**
* Fires the {@link #onRunning(FlowExecution)} event.
*/
- public static void fireRunning(@Nonnull FlowExecution execution) {
+ public static void fireRunning(@NonNull FlowExecution execution) {
for (FlowExecutionListener listener : ExtensionList.lookup(FlowExecutionListener.class)) {
listener.onRunning(execution);
}
@@ -79,7 +80,7 @@ public static void fireRunning(@Nonnull FlowExecution execution) {
/**
* Fires the {@link #onResumed(FlowExecution)} event.
*/
- public static void fireResumed(@Nonnull FlowExecution execution) {
+ public static void fireResumed(@NonNull FlowExecution execution) {
for (FlowExecutionListener listener : ExtensionList.lookup(FlowExecutionListener.class)) {
listener.onResumed(execution);
}
@@ -88,7 +89,7 @@ public static void fireResumed(@Nonnull FlowExecution execution) {
/**
* Fires the {@link #onCompleted(FlowExecution)} event.
*/
- public static void fireCompleted(@Nonnull FlowExecution execution) {
+ public static void fireCompleted(@NonNull FlowExecution execution) {
for (FlowExecutionListener listener : ExtensionList.lookup(FlowExecutionListener.class)) {
listener.onCompleted(execution);
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionOwner.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionOwner.java
index 2a09113e..f8a0e322 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionOwner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionOwner.java
@@ -32,8 +32,8 @@
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import jenkins.model.TransientActionFactory;
import org.jenkinsci.plugins.workflow.log.LogStorage;
import org.jenkinsci.plugins.workflow.steps.StepContext;
@@ -50,7 +50,7 @@ public abstract class FlowExecutionOwner implements Serializable {
* @throws IOException
* if fails to find {@link FlowExecution}.
*/
- @Nonnull
+ @NonNull
public abstract FlowExecution get() throws IOException;
/** Invoked in {@link FlowExecutionList#saveAll()} to notify that execution has been suspended */
@@ -75,7 +75,7 @@ void notifyShutdown() {
}
/**
- * A directory (on the master) where information may be persisted.
+ * A directory (on the controller) where information may be persisted.
* @see Run#getRootDir
*/
public abstract File getRootDir() throws IOException;
@@ -125,7 +125,7 @@ public String getUrlOfExecution() throws IOException {
* The listener should be remotable: if sent to an agent, messages printed to it should still appear in the log.
* The same will then apply to calls to {@link StepContext#get} on {@link TaskListener}.
*/
- public @Nonnull TaskListener getListener() throws IOException {
+ public @NonNull TaskListener getListener() throws IOException {
try {
return LogStorage.of(this).overallListener();
} catch (InterruptedException x) {
@@ -156,6 +156,7 @@ public static FlowExecutionOwner dummyOwner() {
private static class DummyOwner extends FlowExecutionOwner {
DummyOwner() {}
+ @NonNull
@Override public FlowExecution get() throws IOException {
throw new IOException("not implemented");
}
@@ -174,7 +175,8 @@ private static class DummyOwner extends FlowExecutionOwner {
@Override public int hashCode() {
return 0;
}
- @Override public TaskListener getListener() throws IOException {
+ @NonNull
+ @Override public TaskListener getListener() {
return TaskListener.NULL;
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/GlobalDefaultFlowDurabilityLevel.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/GlobalDefaultFlowDurabilityLevel.java
index 3c00074a..d07c751f 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/flow/GlobalDefaultFlowDurabilityLevel.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/GlobalDefaultFlowDurabilityLevel.java
@@ -4,14 +4,18 @@
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.security.Permission;
-import hudson.util.ReflectionUtils;
+import hudson.util.FormValidation;
+import hudson.util.ListBoxModel;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
+import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import java.lang.reflect.InvocationTargetException;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Supports a global default durability level for users
@@ -32,6 +36,7 @@ public DescriptorImpl() {
/** Null to use the platform default, which may change over time as enhanced options are available. */
private FlowDurabilityHint durabilityHint = null;
+ @NonNull
@Override
public String getDisplayName() {
return "Global Default Pipeline Durability Level";
@@ -47,8 +52,17 @@ public void setDurabilityHint(FlowDurabilityHint hint){
save();
}
+ public FormValidation doCheckDurabilityHint(@QueryParameter("durabilityHint") String durabilityHint) {
+ FlowDurabilityHint flowDurabilityHint = Arrays.stream(FlowDurabilityHint.values())
+ .filter(f -> f.name().equals(durabilityHint))
+ .findFirst()
+ .orElse(GlobalDefaultFlowDurabilityLevel.SUGGESTED_DURABILITY_HINT);
+
+ return FormValidation.ok(flowDurabilityHint.getTooltip());
+ }
+
@Override
- public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ public boolean configure(StaplerRequest req, JSONObject json) {
// TODO verify if this is covered by permissions checks or we need an explicit check here.
Object ob = json.opt("durabilityHint");
FlowDurabilityHint hint = null;
@@ -65,29 +79,24 @@ public boolean configure(StaplerRequest req, JSONObject json) throws FormExcepti
return true;
}
- public static FlowDurabilityHint getSuggestedDurabilityHint() {
- return GlobalDefaultFlowDurabilityLevel.SUGGESTED_DURABILITY_HINT;
- }
+ public ListBoxModel doFillDurabilityHintItems() {
+ ListBoxModel options = new ListBoxModel();
- public static FlowDurabilityHint[] getDurabilityHintValues() {
- return FlowDurabilityHint.values();
- }
+ options.add("None: use pipeline default (" + GlobalDefaultFlowDurabilityLevel.SUGGESTED_DURABILITY_HINT.name() + ")", "null");
- @Nonnull
- // TODO: Add @Override when Jenkins core baseline is 2.222+
- public Permission getRequiredGlobalConfigPagePermission() {
- return getJenkinsManageOrAdmin();
+ List mappedOptions = Arrays.stream(FlowDurabilityHint.values())
+ .map(hint -> new ListBoxModel.Option(hint.getDescription(), hint.name()))
+ .collect(Collectors.toList());
+
+ options.addAll(mappedOptions);
+
+ return options;
}
- // TODO: remove when Jenkins core baseline is 2.222+
- Permission getJenkinsManageOrAdmin() {
- Permission manage;
- try { // Manage is available starting from Jenkins 2.222 (https://jenkins.io/changelog/#v2.222). See JEP-223 for more info
- manage = (Permission) ReflectionUtils.getPublicProperty(Jenkins.get(), "MANAGE");
- } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
- manage = Jenkins.ADMINISTER;
- }
- return manage;
+ @NonNull
+ @Override
+ public Permission getRequiredGlobalConfigPagePermission() {
+ return Jenkins.MANAGE;
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/MoreExecutors.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/MoreExecutors.java
new file mode 100644
index 00000000..1d95f6f0
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/MoreExecutors.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Guava 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.jenkinsci.plugins.workflow.flow;
+
+import com.google.common.annotations.GwtCompatible;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Factory and utility methods for {@link java.util.concurrent.Executor}, {@link ExecutorService},
+ * and {@link java.util.concurrent.ThreadFactory}.
+ *
+ * @author Eric Fellheimer
+ * @author Kyle Littlefield
+ * @author Justin Mahoney
+ * @since 3.0
+ */
+@GwtCompatible(emulated = true)
+@Restricted(NoExternalUse.class)
+public final class MoreExecutors {
+ private MoreExecutors() {}
+
+ /**
+ * Returns an {@link Executor} that runs each task in the thread that invokes {@link
+ * Executor#execute execute}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}.
+ *
+ * This executor is appropriate for tasks that are lightweight and not deeply chained.
+ * Inappropriate {@code directExecutor} usage can cause problems, and these problems can be
+ * difficult to reproduce because they depend on timing. For example:
+ *
+ *
+ * - A call like {@code future.transform(function, directExecutor())} may execute the function
+ * immediately in the thread that is calling {@code transform}. (This specific case happens
+ * if the future is already completed.) If {@code transform} call was made from a UI thread
+ * or other latency-sensitive thread, a heavyweight function can harm responsiveness.
+ *
- If the task will be executed later, consider which thread will trigger the execution --
+ * since that thread will execute the task inline. If the thread is a shared system thread
+ * like an RPC network thread, a heavyweight task can stall progress of the whole system or
+ * even deadlock it.
+ *
- If many tasks will be triggered by the same event, one heavyweight task may delay other
+ * tasks -- even tasks that are not themselves {@code directExecutor} tasks.
+ *
- If many such tasks are chained together (such as with {@code
+ * future.transform(...).transform(...).transform(...)....}), they may overflow the stack.
+ * (In simple cases, callers can avoid this by registering all tasks with the same {@code
+ * MoreExecutors#newSequentialExecutor} wrapper around {@code directExecutor()}. More
+ * complex cases may require using thread pools or making deeper changes.)
+ *
+ *
+ * Additionally, beware of executing tasks with {@code directExecutor} while holding a lock. Since
+ * the task you submit to the executor (or any other arbitrary work the executor does) may do slow
+ * work or acquire other locks, you risk deadlocks.
+ *
+ * This instance is equivalent to:
+ *
+ *
{@code
+ * final class DirectExecutor implements Executor {
+ * public void execute(Runnable r) {
+ * r.run();
+ * }
+ * }
+ * }
+ *
+ * This should be preferred to {@code #newDirectExecutorService()} because implementing the
+ * {@link ExecutorService} subinterface necessitates significant performance overhead.
+ *
+ *
+ * @since 18.0
+ */
+ public static Executor directExecutor() {
+ return DirectExecutor.INSTANCE;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/StashManager.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/StashManager.java
index bdbc0d01..996175df 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/flow/StashManager.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/StashManager.java
@@ -50,8 +50,9 @@
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.nio.file.Files;
import jenkins.model.ArtifactManager;
import jenkins.model.Jenkins;
import jenkins.util.BuildListenerAdapter;
@@ -77,19 +78,19 @@
public class StashManager {
@Deprecated
- public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull TaskListener listener,
+ public static void stash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull TaskListener listener,
@CheckForNull String includes, @CheckForNull String excludes) throws IOException, InterruptedException {
stash(build, name, workspace, listener, includes, excludes, true, false);
}
@Deprecated
- public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull TaskListener listener,
+ public static void stash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull TaskListener listener,
@CheckForNull String includes, @CheckForNull String excludes, boolean useDefaultExcludes) throws IOException, InterruptedException {
stash(build, name, workspace, listener, includes, excludes, useDefaultExcludes, false);
}
@Deprecated
- public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull TaskListener listener,
+ public static void stash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull TaskListener listener,
@CheckForNull String includes, @CheckForNull String excludes, boolean useDefaultExcludes, boolean allowEmpty) throws IOException, InterruptedException {
stash(build, name, workspace, launcherFor(workspace, listener), envFor(build, workspace, listener), listener, includes, excludes, useDefaultExcludes, allowEmpty);
}
@@ -109,7 +110,7 @@ public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull
* @see StashAwareArtifactManager#stash
*/
@SuppressFBWarnings(value="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification="fine if mkdirs returns false")
- public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull EnvVars env, @Nonnull TaskListener listener,
+ public static void stash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull EnvVars env, @NonNull TaskListener listener,
@CheckForNull String includes, @CheckForNull String excludes, boolean useDefaultExcludes, boolean allowEmpty) throws IOException, InterruptedException {
Jenkins.checkGoodName(name);
StashAwareArtifactManager saam = stashAwareArtifactManager(build);
@@ -132,7 +133,7 @@ public static void stash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull
}
@Deprecated
- public static void unstash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ public static void unstash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull TaskListener listener) throws IOException, InterruptedException {
unstash(build, name, workspace, launcherFor(workspace, listener), envFor(build, workspace, listener), listener);
}
@@ -147,7 +148,7 @@ public static void unstash(@Nonnull Run,?> build, @Nonnull String name, @Nonnu
* @throws AbortException in case there is no such saved stash
* @see StashAwareArtifactManager#unstash
*/
- public static void unstash(@Nonnull Run,?> build, @Nonnull String name, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull EnvVars env, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ public static void unstash(@NonNull Run,?> build, @NonNull String name, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull EnvVars env, @NonNull TaskListener listener) throws IOException, InterruptedException {
Jenkins.checkGoodName(name);
StashAwareArtifactManager saam = stashAwareArtifactManager(build);
if (saam != null) {
@@ -162,7 +163,7 @@ public static void unstash(@Nonnull Run,?> build, @Nonnull String name, @Nonnu
}
@Deprecated
- public static void clearAll(@Nonnull Run,?> build) throws IOException {
+ public static void clearAll(@NonNull Run,?> build) throws IOException {
try {
clearAll(build, TaskListener.NULL);
} catch (InterruptedException x) {
@@ -176,7 +177,7 @@ public static void clearAll(@Nonnull Run,?> build) throws IOException {
* @param listener a way to report progress or problems
* @see StashAwareArtifactManager#clearAllStashes
*/
- public static void clearAll(@Nonnull Run,?> build, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ public static void clearAll(@NonNull Run,?> build, @NonNull TaskListener listener) throws IOException, InterruptedException {
StashAwareArtifactManager saam = stashAwareArtifactManager(build);
if (saam != null) {
saam.clearAllStashes(listener);
@@ -186,7 +187,7 @@ public static void clearAll(@Nonnull Run,?> build, @Nonnull TaskListener liste
}
@Deprecated
- public static void maybeClearAll(@Nonnull Run,?> build) throws IOException {
+ public static void maybeClearAll(@NonNull Run,?> build) throws IOException {
try {
maybeClearAll(build, TaskListener.NULL);
} catch (InterruptedException x) {
@@ -200,7 +201,7 @@ public static void maybeClearAll(@Nonnull Run,?> build) throws IOException {
* @param build a build possibly passed to {@link #stash} in the past
* @see #clearAll(Run, TaskListener)
*/
- public static void maybeClearAll(@Nonnull Run,?> build, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ public static void maybeClearAll(@NonNull Run,?> build, @NonNull TaskListener listener) throws IOException, InterruptedException {
for (StashBehavior behavior : ExtensionList.lookup(StashBehavior.class)) {
if (!behavior.shouldClearAll(build)) {
return;
@@ -213,7 +214,7 @@ public static void maybeClearAll(@Nonnull Run,?> build, @Nonnull TaskListener
* @deprecated without replacement; only used from {@link CopyStashesAndArtifacts} anyway
*/
@Deprecated
- public static void copyAll(@Nonnull Run,?> from, @Nonnull Run,?> to) throws IOException {
+ public static void copyAll(@NonNull Run,?> from, @NonNull Run,?> to) throws IOException {
File fromStorage = storage(from);
if (!fromStorage.isDirectory()) {
return;
@@ -223,7 +224,7 @@ public static void copyAll(@Nonnull Run,?> from, @Nonnull Run,?> to) throws
@Restricted(DoNotUse.class) // just for tests, and incompatible with StashAwareArtifactManager
@SuppressFBWarnings(value="DM_DEFAULT_ENCODING", justification="test code")
- public static Map> stashesOf(@Nonnull Run,?> build) throws IOException {
+ public static Map> stashesOf(@NonNull Run,?> build) throws IOException {
Map> result = new TreeMap<>();
File[] kids = storage(build).listFiles();
if (kids != null) {
@@ -248,12 +249,12 @@ public static Map> stashesOf(@Nonnull Run,?> build)
return result;
}
- private static @Nonnull File storage(@Nonnull Run,?> build) throws IOException {
+ private static @NonNull File storage(@NonNull Run,?> build) throws IOException {
assert stashAwareArtifactManager(build) == null;
return new File(build.getRootDir(), "stashes");
}
- private static @Nonnull File storage(@Nonnull Run,?> build, @Nonnull String name) throws IOException {
+ private static @NonNull File storage(@NonNull Run,?> build, @NonNull String name) throws IOException {
File dir = storage(build);
File f = new File(dir, name + SUFFIX);
if (!f.getParentFile().equals(dir)) {
@@ -276,7 +277,7 @@ public static abstract class StashBehavior implements ExtensionPoint {
* @param build a build which has finished
* @return true (the default) to go ahead and call {@link #clearAll}, false to stop
*/
- public boolean shouldClearAll(@Nonnull Run,?> build) {
+ public boolean shouldClearAll(@NonNull Run,?> build) {
return true;
}
@@ -298,13 +299,13 @@ public boolean shouldClearAll(@Nonnull Run,?> build) {
public interface StashAwareArtifactManager /* extends ArtifactManager */ {
/** @see StashManager#stash(Run, String, FilePath, Launcher, EnvVars, TaskListener, String, String, boolean, boolean) */
- void stash(@Nonnull String name, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull EnvVars env, @Nonnull TaskListener listener, @CheckForNull String includes, @CheckForNull String excludes, boolean useDefaultExcludes, boolean allowEmpty) throws IOException, InterruptedException;
+ void stash(@NonNull String name, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull EnvVars env, @NonNull TaskListener listener, @CheckForNull String includes, @CheckForNull String excludes, boolean useDefaultExcludes, boolean allowEmpty) throws IOException, InterruptedException;
/** @see StashManager#unstash(Run, String, FilePath, Launcher, EnvVars, TaskListener) */
- void unstash(@Nonnull String name, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull EnvVars env, @Nonnull TaskListener listener) throws IOException, InterruptedException;
+ void unstash(@NonNull String name, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull EnvVars env, @NonNull TaskListener listener) throws IOException, InterruptedException;
/** @see StashManager#clearAll(Run, TaskListener) */
- void clearAllStashes(@Nonnull TaskListener listener) throws IOException, InterruptedException;
+ void clearAllStashes(@NonNull TaskListener listener) throws IOException, InterruptedException;
/**
* Copy all stashes and artifacts from one build to another.
@@ -312,17 +313,17 @@ public interface StashAwareArtifactManager /* extends ArtifactManager */ {
* If the implementation cannot handle {@code to} for whatever reason, it may throw {@link AbortException}.
* @see CopyStashesAndArtifacts
*/
- void copyAllArtifactsAndStashes(@Nonnull Run,?> to, @Nonnull TaskListener listener) throws IOException, InterruptedException;
+ void copyAllArtifactsAndStashes(@NonNull Run,?> to, @NonNull TaskListener listener) throws IOException, InterruptedException;
}
- private static @CheckForNull StashAwareArtifactManager stashAwareArtifactManager(@Nonnull Run, ?> build) throws IOException {
+ private static @CheckForNull StashAwareArtifactManager stashAwareArtifactManager(@NonNull Run, ?> build) throws IOException {
ArtifactManager am = build.pickArtifactManager();
return am instanceof StashAwareArtifactManager ? (StashAwareArtifactManager) am : null;
}
@Deprecated
- private static @Nonnull Launcher launcherFor(@Nonnull FilePath workspace, @Nonnull TaskListener listener) {
+ private static @NonNull Launcher launcherFor(@NonNull FilePath workspace, @NonNull TaskListener listener) {
Computer c = workspace.toComputer();
if (c != null) {
Node n = c.getNode();
@@ -339,7 +340,7 @@ public interface StashAwareArtifactManager /* extends ArtifactManager */ {
}
@Deprecated
- private static @Nonnull EnvVars envFor(@Nonnull Run, ?> build, @Nonnull FilePath workspace, @Nonnull TaskListener listener) throws IOException, InterruptedException {
+ private static @NonNull EnvVars envFor(@NonNull Run, ?> build, @NonNull FilePath workspace, @NonNull TaskListener listener) throws IOException, InterruptedException {
Computer c = workspace.toComputer();
if (c != null) {
EnvVars e = c.getEnvironment();
@@ -362,16 +363,13 @@ public interface StashAwareArtifactManager /* extends ArtifactManager */ {
return;
}
VirtualFile srcroot = original.getArtifactManager().root();
- FilePath dstDir = createTmpDir();
+ FilePath dstDir = new FilePath(Files.createTempDirectory("artifact-copy").toFile());
try {
Map files = new HashMap<>();
for (String path : srcroot.list("**/*", null, false)) {
files.put(path, path);
- InputStream in = srcroot.child(path).open();
- try {
+ try(InputStream in = srcroot.child(path).open()) {
dstDir.child(path).copyFrom(in);
- } finally {
- IOUtils.closeQuietly(in);
}
}
if (!files.isEmpty()) {
@@ -385,14 +383,6 @@ public interface StashAwareArtifactManager /* extends ArtifactManager */ {
StashManager.copyAll(original, copy);
}
- private FilePath createTmpDir() throws IOException {
- File dir = File.createTempFile("artifact", "copy");
- if (!(dir.delete() && dir.mkdirs())) {
- throw new IOException("Failed to create temporary directory " + dir.getPath());
- }
- return new FilePath(dir);
- }
-
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/StepListener.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/StepListener.java
index ac6bb7fb..e514aa9d 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/flow/StepListener.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/StepListener.java
@@ -29,7 +29,7 @@
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* {@link StepListener}s are fired before invoking a step but after the {@link FlowNode} has been created and the
@@ -41,5 +41,5 @@ public interface StepListener extends ExtensionPoint {
* Called before a {@link Step} is invoked, but after its {@link FlowNode} and {@link StepContext} have been created.
* Listeners can make the step fail by calling {@link StepContext#onFailure}.
*/
- void notifyOfNewStep(@Nonnull Step step, @Nonnull StepContext context);
+ void notifyOfNewStep(@NonNull Step step, @NonNull StepContext context);
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockEndNode.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockEndNode.java
index 2c0d2494..bcfe4bea 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockEndNode.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockEndNode.java
@@ -28,7 +28,7 @@
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import java.util.List;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* End of a block.
@@ -55,7 +55,7 @@ public BlockEndNode(FlowExecution exec, String id, START start, List p
* @return an earlier node matching this block
* @throws IllegalStateException if the start node could not be reloaded after deserialization
*/
- public @Nonnull START getStartNode() {
+ public @NonNull START getStartNode() {
if (start == null) {
try {
start = (START) getExecution().getNode(startId);
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockStartNode.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockStartNode.java
index 69553974..b40135ae 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockStartNode.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/BlockStartNode.java
@@ -26,7 +26,7 @@
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.util.List;
/**
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowNode.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowNode.java
index 271ef53f..c5655a67 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowNode.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowNode.java
@@ -43,8 +43,8 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.actions.LabelAction;
@@ -143,14 +143,14 @@ public final boolean isActive() {
return getPersistentAction(ErrorAction.class);
}
- public @Nonnull FlowExecution getExecution() {
+ public @NonNull FlowExecution getExecution() {
return exec;
}
/**
* Returns a read-only view of parents.
*/
- @Nonnull
+ @NonNull
public List getParents() {
if (parents == null) {
parents = loadParents(parentIds);
@@ -158,7 +158,7 @@ public List getParents() {
return parents;
}
- @Nonnull
+ @NonNull
private List loadParents(List parentIds) {
try {
if (parentIds.size() == 1) {
@@ -190,14 +190,14 @@ public String getEnclosingId() {
* Get the list of enclosing {@link BlockStartNode}s, starting from innermost, for this node.
* May be empty if we are the {@link FlowStartNode} or {@link FlowEndNode}
*/
- @Nonnull
+ @NonNull
public List extends BlockStartNode> getEnclosingBlocks() {
return this.exec.findAllEnclosingBlockStarts(this);
}
/** Return an iterator over all enclosing blocks, from the nearest-enclosing outward ("inside-out" order).
* Prefer this to {@link #getEnclosingBlocks()} unless you need ALL nodes, because it can evaluate lazily. */
- @Nonnull
+ @NonNull
public Iterable iterateEnclosingBlocks() {
return this.exec.iterateEnclosingBlocks(this);
}
@@ -205,7 +205,7 @@ public Iterable iterateEnclosingBlocks() {
/**
* Returns a read-only view of the IDs for enclosing blocks of this flow node, innermost first. May be empty.
*/
- @Nonnull
+ @NonNull
public List getAllEnclosingIds() {
List extends BlockStartNode> nodes = getEnclosingBlocks();
ArrayList output = new ArrayList<>(nodes.size());
@@ -217,7 +217,7 @@ public List getAllEnclosingIds() {
@Restricted(DoNotUse.class)
@Exported(name="parents")
- @Nonnull
+ @NonNull
public List getParentIds() {
if (parentIds != null) {
return Collections.unmodifiableList(parentIds);
@@ -297,7 +297,7 @@ public BallColor getIconColor() {
return c;
}
- private static BallColor resultToBallColor(@Nonnull Result result) {
+ private static BallColor resultToBallColor(@NonNull Result result) {
if (result == Result.SUCCESS) {
return BallColor.BLUE;
} else if (result == Result.UNSTABLE) {
@@ -370,7 +370,7 @@ protected synchronized void setActions(List actions) {
* @return First nontransient action or null if not found.
*/
@CheckForNull
- public final T getPersistentAction(@Nonnull Class type) {
+ public final T getPersistentAction(@NonNull Class type) {
loadActions();
for (Action a : actions) {
if (type.isInstance(a)) {
@@ -416,6 +416,7 @@ private synchronized void loadActions() {
}
@Exported
+ @NonNull
@SuppressWarnings("deprecation") // of override
@Override
@SuppressFBWarnings(value = "UG_SYNC_SET_UNSYNC_GET", justification = "CopyOnWrite ArrayList, and field load & modification is synchronized")
@@ -466,7 +467,7 @@ public int size() {
}
@Override
- public boolean removeAll(Collection> c) {
+ public boolean removeAll(@NonNull Collection> c) {
boolean changed = actions.removeAll(c);
if (changed) {
persistSafe();
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowStartNode.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowStartNode.java
index 96d3f916..f93c1249 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowStartNode.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/FlowStartNode.java
@@ -24,6 +24,7 @@
package org.jenkinsci.plugins.workflow.graph;
+import edu.umd.cs.findbugs.annotations.NonNull;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import java.util.List;
@@ -44,6 +45,7 @@ public FlowStartNode(FlowExecution exec, String id) {
* @deprecated
* Why are you calling a method that always return empty list?
*/
+ @NonNull
@Override
public List getParents() {
return super.getParents();
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/GraphLookupView.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/GraphLookupView.java
index 9573a94c..2b77ff7e 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/GraphLookupView.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/GraphLookupView.java
@@ -1,7 +1,7 @@
package org.jenkinsci.plugins.workflow.graph;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -21,13 +21,13 @@
*/
public interface GraphLookupView {
/** Tests if the node is a currently running head, or the start of a block that has not completed executing */
- boolean isActive(@Nonnull FlowNode node);
+ boolean isActive(@NonNull FlowNode node);
/** Find the end node corresponding to a start node, and can be used to tell if the block is completed.
* @return {@link BlockEndNode} matching the given start node, or null if block hasn't completed
*/
@CheckForNull
- BlockEndNode getEndNode(@Nonnull BlockStartNode startNode);
+ BlockEndNode getEndNode(@NonNull BlockStartNode startNode);
/**
* Find the immediately enclosing {@link BlockStartNode} around a {@link FlowNode}
@@ -35,7 +35,7 @@ public interface GraphLookupView {
* @return Null if node is a {@link FlowStartNode} or {@link FlowEndNode}
*/
@CheckForNull
- BlockStartNode findEnclosingBlockStart(@Nonnull FlowNode node);
+ BlockStartNode findEnclosingBlockStart(@NonNull FlowNode node);
/**
* Provide an {@link Iterable} over all enclosing blocks, which can be used similarly to {@link #findAllEnclosingBlockStarts(FlowNode)} but
@@ -45,15 +45,15 @@ public interface GraphLookupView {
* @param node Node to find enclosing blocks for
* @return Iterable over enclosing blocks, from the nearest-enclosing outward ("inside-out" order)
*/
- Iterable iterateEnclosingBlocks(@Nonnull FlowNode node);
+ Iterable iterateEnclosingBlocks(@NonNull FlowNode node);
/** Return all enclosing block start nodes, as with {@link #findEnclosingBlockStart(FlowNode)}.
* Usage note:Prefer using {@link #iterateEnclosingBlocks(FlowNode)} unless you know you need ALL blocks, since that can lazy-load.
* @param node Node to find enclosing blocks for
* @return All enclosing block starts from the nearest-enclosing outward ("inside-out" order), or EMPTY_LIST if this is a start or end node
*/
- @Nonnull
- List findAllEnclosingBlockStarts(@Nonnull FlowNode node);
+ @NonNull
+ List findAllEnclosingBlockStarts(@NonNull FlowNode node);
/** Provides a trivial implementation to facilitate implementing {@link #iterateEnclosingBlocks(FlowNode)}*/
class EnclosingBlocksIterable implements Iterable {
@@ -90,7 +90,7 @@ public void remove() {
}
}
- public EnclosingBlocksIterable(@Nonnull GraphLookupView view, @Nonnull FlowNode node) {
+ public EnclosingBlocksIterable(@NonNull GraphLookupView view, @NonNull FlowNode node) {
this.view = view;
this.node = node;
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java
index 5d6e2ffc..e721410a 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java
@@ -6,17 +6,17 @@
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Provides overall insight into the structure of a flow graph... but with limited visibility so we can change implementation.
- * Designed to work entirely on the basis of the {@link FlowNode#id} rather than the {@link FlowNode}s themselves.
+ * Designed to work entirely on the basis of the {@link FlowNode#getId()} rather than the {@link FlowNode}s themselves.
*/
@SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "Can can use instance identity when comparing to a final constant")
@Restricted(NoExternalUse.class)
@@ -25,10 +25,10 @@ public final class StandardGraphLookupView implements GraphLookupView, GraphList
static final String INCOMPLETE = "";
/** Map the blockStartNode to its endNode, to accellerate a range of operations */
- HashMap blockStartToEnd = new HashMap<>();
+ ConcurrentHashMap blockStartToEnd = new ConcurrentHashMap<>();
/** Map a node to its nearest enclosing block */
- HashMap nearestEnclosingBlock = new HashMap<>();
+ ConcurrentHashMap nearestEnclosingBlock = new ConcurrentHashMap<>();
public void clearCache() {
blockStartToEnd.clear();
@@ -37,12 +37,13 @@ public void clearCache() {
/** Update with a new node added to the flowgraph */
@Override
- public void onNewHead(@Nonnull FlowNode newHead) {
+ public void onNewHead(@NonNull FlowNode newHead) {
if (newHead instanceof BlockEndNode) {
- blockStartToEnd.put(((BlockEndNode)newHead).getStartNode().getId(), newHead.getId());
- String overallEnclosing = nearestEnclosingBlock.get(((BlockEndNode) newHead).getStartNode().getId());
- if (overallEnclosing != null) {
- nearestEnclosingBlock.put(newHead.getId(), overallEnclosing);
+ String startNodeId = ((BlockEndNode)newHead).getStartNode().getId();
+ blockStartToEnd.put(startNodeId, newHead.getId());
+ String enclosingId = nearestEnclosingBlock.get(startNodeId);
+ if (enclosingId != null) {
+ nearestEnclosingBlock.put(newHead.getId(), enclosingId);
}
} else {
if (newHead instanceof BlockStartNode) {
@@ -76,7 +77,7 @@ public StandardGraphLookupView() {
}
@Override
- public boolean isActive(@Nonnull FlowNode node) {
+ public boolean isActive(@NonNull FlowNode node) {
if (node instanceof FlowEndNode) { // cf. JENKINS-26139
return !node.getExecution().isComplete();
} else if (node instanceof BlockStartNode){ // BlockStartNode
@@ -87,7 +88,7 @@ public boolean isActive(@Nonnull FlowNode node) {
}
// Do a brute-force scan for the block end matching the start, caching info along the way for future use
- BlockEndNode bruteForceScanForEnd(@Nonnull BlockStartNode start) {
+ BlockEndNode bruteForceScanForEnd(@NonNull BlockStartNode start) {
DepthFirstScanner scan = new DepthFirstScanner();
scan.setup(start.getExecution().getCurrentHeads());
for (FlowNode f : scan) {
@@ -116,7 +117,7 @@ BlockEndNode bruteForceScanForEnd(@Nonnull BlockStartNode start) {
/** Do a brute-force scan for the enclosing blocks **/
- BlockStartNode bruteForceScanForEnclosingBlock(@Nonnull final FlowNode node) {
+ BlockStartNode bruteForceScanForEnclosingBlock(@NonNull final FlowNode node) {
FlowNode current = node;
while (!(current instanceof FlowStartNode)) { // Hunt back for enclosing blocks, a potentially expensive operation
@@ -157,7 +158,7 @@ BlockStartNode bruteForceScanForEnclosingBlock(@Nonnull final FlowNode node) {
@CheckForNull
@Override
- public BlockEndNode getEndNode(@Nonnull final BlockStartNode startNode) {
+ public BlockEndNode getEndNode(@NonNull final BlockStartNode startNode) {
String id = blockStartToEnd.get(startNode.getId());
if (id != null) {
@@ -167,17 +168,15 @@ public BlockEndNode getEndNode(@Nonnull final BlockStartNode startNode) {
throw new RuntimeException(ioe);
}
} else {
- BlockEndNode node = bruteForceScanForEnd(startNode);
- if (node != null) {
- blockStartToEnd.put(startNode.getId(), node.getId());
- }
- return node;
+ // returns the end node or null
+ // if this returns end node, it also adds start and end to blockStartToEnd
+ return bruteForceScanForEnd(startNode);
}
}
@CheckForNull
@Override
- public BlockStartNode findEnclosingBlockStart(@Nonnull FlowNode node) {
+ public BlockStartNode findEnclosingBlockStart(@NonNull FlowNode node) {
if (node instanceof FlowStartNode || node instanceof FlowEndNode) {
return null;
}
@@ -191,22 +190,18 @@ public BlockStartNode findEnclosingBlockStart(@Nonnull FlowNode node) {
}
}
- BlockStartNode enclosing = bruteForceScanForEnclosingBlock(node);
- if (enclosing != null) {
- nearestEnclosingBlock.put(node.getId(), enclosing.getId());
- return enclosing;
- }
- return null;
+ // when scan completes, enclosing is in the cache if it exists
+ return bruteForceScanForEnclosingBlock(node);
}
@Override
- public Iterable iterateEnclosingBlocks(@Nonnull FlowNode node) {
+ public Iterable iterateEnclosingBlocks(@NonNull FlowNode node) {
return new EnclosingBlocksIterable(this, node);
}
- @Nonnull
+ @NonNull
@Override
- public List findAllEnclosingBlockStarts(@Nonnull FlowNode node) {
+ public List findAllEnclosingBlockStarts(@NonNull FlowNode node) {
if (node instanceof FlowStartNode || node instanceof FlowEndNode) {
return Collections.emptyList();
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/StepNode.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/StepNode.java
index 9c24b67b..81db7d4d 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graph/StepNode.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/StepNode.java
@@ -26,7 +26,7 @@
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
/**
* Optional interface for a {@link FlowNode} that has an associated {@link StepDescriptor}.
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/AbstractFlowScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/AbstractFlowScanner.java
index 088b5bb5..c0b3e328 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/AbstractFlowScanner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/AbstractFlowScanner.java
@@ -28,9 +28,9 @@
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.NotThreadSafe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,8 +57,8 @@
*
*
* All APIs visit the parent nodes, walking backward from heads(inclusive) until they they hit {@link #myBlackList} nodes (exclusive) or reach the end of the DAG.
- * If blackList nodes are an empty collection or null, APIs will walk to the beginning of the FlowGraph.
- * Multiple blackList nodes are helpful for putting separate bounds on walking different parallel branches.
+ * If denyList nodes are an empty collection or null, APIs will walk to the beginning of the FlowGraph.
+ * Multiple denyList nodes are helpful for putting separate bounds on walking different parallel branches.
*
*
Key Points:
*
- There are many helper methods offering syntactic sugar for the above APIs in common use cases (simpler method signatures).
@@ -83,7 +83,7 @@
* - Scan through all nodes *just* within a block
*
* - Use the {@link org.jenkinsci.plugins.workflow.graph.BlockEndNode} as the head
- * - Use the {@link org.jenkinsci.plugins.workflow.graph.BlockStartNode} as its blacklist with {@link Collections#singleton(Object)}
+ * - Use the {@link org.jenkinsci.plugins.workflow.graph.BlockStartNode} as its denylist with {@link Collections#singleton(Object)}
*
*
*
@@ -99,11 +99,11 @@ public abstract class AbstractFlowScanner implements Iterable , Filter
protected Collection myBlackList = Collections.emptySet();
- /** When checking for blacklist membership, we convert to a hashset when checking more than this many elements */
+ /** When checking for denylist membership, we convert to a hashset when checking more than this many elements */
protected static final int MAX_LIST_CHECK_SIZE = 5;
/** Helper: convert stop nodes to a collection that can efficiently be checked for membership, handling null if needed */
- @Nonnull
+ @NonNull
protected Collection convertToFastCheckable(@CheckForNull Collection nodeCollection) {
if (nodeCollection == null || nodeCollection.size()==0) {
return Collections.emptySet();
@@ -142,7 +142,7 @@ public boolean setup(@CheckForNull Collection heads, @CheckForNull Col
}
/**
- * Helper: version of {@link #setup(Collection, Collection)} where we don't have any nodes to blacklist
+ * Helper: version of {@link #setup(Collection, Collection)} where we don't have any nodes to denylist
*/
public boolean setup(@CheckForNull Collection heads) {
if (heads == null) {
@@ -152,7 +152,7 @@ public boolean setup(@CheckForNull Collection heads) {
}
/**
- * Helper: version of {@link #setup(Collection, Collection)} where we don't have any nodes to blacklist, and have just a single head
+ * Helper: version of {@link #setup(Collection, Collection)} where we don't have any nodes to denylist, and have just a single head
*/
public boolean setup(@CheckForNull FlowNode head, @CheckForNull Collection blackList) {
if (head == null) {
@@ -162,7 +162,7 @@ public boolean setup(@CheckForNull FlowNode head, @CheckForNull Collection filteredHeads);
+ protected abstract void setHeads(@NonNull Collection filteredHeads);
/**
* Actual meat of the iteration, get the next node to visit, using and updating state as needed
@@ -194,7 +194,7 @@ public boolean setup(@CheckForNull FlowNode head) {
* @return Next node to visit, or null if we've exhausted the node list
*/
@CheckForNull
- protected abstract FlowNode next(@Nonnull FlowNode current, @Nonnull Collection blackList);
+ protected abstract FlowNode next(@NonNull FlowNode current, @NonNull Collection blackList);
@Override
public boolean hasNext() {
@@ -218,7 +218,7 @@ public final void remove() {
}
@Override
- @Nonnull
+ @NonNull
public Iterator iterator() {
return this;
}
@@ -229,8 +229,8 @@ public Iterator iterator() {
* @return A {@link Filterator} against this FlowScanner, which can be filtered in additional ways.
*/
@Override
- @Nonnull
- public Filterator filter(@Nonnull Predicate filterCondition) {
+ @NonNull
+ public Filterator filter(@NonNull Predicate filterCondition) {
return new FilteratorImpl<>(this, filterCondition);
}
@@ -261,21 +261,21 @@ public FlowNode findFirstMatch(@CheckForNull Collection heads,
// Polymorphic methods for syntactic sugar
- /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} where there is no blackList */
+ /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} where there is no denyList */
@CheckForNull
- public FlowNode findFirstMatch(@CheckForNull Collection heads, @Nonnull Predicate matchPredicate) {
+ public FlowNode findFirstMatch(@CheckForNull Collection heads, @NonNull Predicate matchPredicate) {
return this.findFirstMatch(heads, null, matchPredicate);
}
- /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} where there is a single head and no blackList */
+ /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} where there is a single head and no denyList */
@CheckForNull
- public FlowNode findFirstMatch(@CheckForNull FlowNode head, @Nonnull Predicate matchPredicate) {
+ public FlowNode findFirstMatch(@CheckForNull FlowNode head, @NonNull Predicate matchPredicate) {
return this.findFirstMatch(Collections.singleton(head), null, matchPredicate);
}
- /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} using {@link FlowExecution#getCurrentHeads()} to get heads and no blackList */
+ /** Syntactic sugar for {@link #findFirstMatch(Collection, Collection, Predicate)} using {@link FlowExecution#getCurrentHeads()} to get heads and no denyList */
@CheckForNull
- public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @Nonnull Predicate matchPredicate) {
+ public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @NonNull Predicate matchPredicate) {
if (exec != null && exec.getCurrentHeads() != null && !exec.getCurrentHeads().isEmpty()) {
return this.findFirstMatch(exec.getCurrentHeads(), null, matchPredicate);
}
@@ -290,7 +290,7 @@ public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @Nonnull Predic
* @param matchCondition Predicate that must be met for nodes to be included in output. Input is always non-null.
* @return List of flownodes matching the predicate.
*/
- @Nonnull
+ @NonNull
public List filteredNodes(@CheckForNull Collection heads,
@CheckForNull Collection blackList,
Predicate matchCondition) {
@@ -308,7 +308,7 @@ public List filteredNodes(@CheckForNull Collection heads,
}
/** Convenience method to get the list all flownodes in the iterator order. */
- @Nonnull
+ @NonNull
public List allNodes(@CheckForNull Collection heads) {
if (!setup(heads)) {
return Collections.emptyList();
@@ -321,25 +321,25 @@ public List allNodes(@CheckForNull Collection heads) {
}
/** Convenience method to get the list of all {@link FlowNode}s for the execution, in iterator order. */
- @Nonnull
+ @NonNull
public List allNodes(@CheckForNull FlowExecution exec) {
return (exec == null) ? Collections.emptyList() : allNodes(exec.getCurrentHeads());
}
- /** Syntactic sugar for {@link #filteredNodes(Collection, Collection, Predicate)} with no blackList nodes */
- @Nonnull
- public List filteredNodes(@CheckForNull Collection heads, @Nonnull Predicate matchPredicate) {
+ /** Syntactic sugar for {@link #filteredNodes(Collection, Collection, Predicate)} with no denyList nodes */
+ @NonNull
+ public List filteredNodes(@CheckForNull Collection heads, @NonNull Predicate matchPredicate) {
return this.filteredNodes(heads, null, matchPredicate);
}
- /** Syntactic sugar for {@link #filteredNodes(Collection, Collection, Predicate)} with a single head and no blackList nodes */
- @Nonnull
- public List filteredNodes(@CheckForNull FlowNode head, @Nonnull Predicate matchPredicate) {
+ /** Syntactic sugar for {@link #filteredNodes(Collection, Collection, Predicate)} with a single head and no denyList nodes */
+ @NonNull
+ public List filteredNodes(@CheckForNull FlowNode head, @NonNull Predicate matchPredicate) {
return this.filteredNodes(Collections.singleton(head), null, matchPredicate);
}
- @Nonnull
- public List filteredNodes(@CheckForNull FlowExecution exec, @Nonnull Predicate matchPredicate) {
+ @NonNull
+ public List filteredNodes(@CheckForNull FlowExecution exec, @NonNull Predicate matchPredicate) {
if (exec == null) {
return Collections.emptyList();
}
@@ -356,7 +356,7 @@ public List filteredNodes(@CheckForNull FlowExecution exec, @Nonnull P
* @param blackList Nodes we can't visit or pass beyond.
* @param visitor Visitor that will see each FlowNode encountered.
*/
- public void visitAll(@CheckForNull Collection heads, @CheckForNull Collection blackList, @Nonnull FlowNodeVisitor visitor) {
+ public void visitAll(@CheckForNull Collection heads, @CheckForNull Collection blackList, @NonNull FlowNodeVisitor visitor) {
if (!setup(heads, blackList)) {
return;
}
@@ -368,8 +368,8 @@ public void visitAll(@CheckForNull Collection heads, @CheckForNull Col
}
}
- /** Syntactic sugar for {@link #visitAll(Collection, Collection, FlowNodeVisitor)} where we don't blacklist any nodes */
- public void visitAll(@CheckForNull Collection heads, @Nonnull FlowNodeVisitor visitor) {
+ /** Syntactic sugar for {@link #visitAll(Collection, Collection, FlowNodeVisitor)} where we don't denylist any nodes */
+ public void visitAll(@CheckForNull Collection heads, @NonNull FlowNodeVisitor visitor) {
visitAll(heads, null, visitor);
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/BlockChunkFinder.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/BlockChunkFinder.java
index f3b4f168..e2ad8023 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/BlockChunkFinder.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/BlockChunkFinder.java
@@ -4,8 +4,8 @@
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Matches start and end of a block. Any block!
@@ -21,12 +21,12 @@ public boolean isStartInsideChunk() {
}
@Override
- public boolean isChunkStart(@Nonnull FlowNode current, @CheckForNull FlowNode previous) {
+ public boolean isChunkStart(@NonNull FlowNode current, @CheckForNull FlowNode previous) {
return current instanceof BlockStartNode;
}
@Override
- public boolean isChunkEnd(@Nonnull FlowNode current, @CheckForNull FlowNode previous) {
+ public boolean isChunkEnd(@NonNull FlowNode current, @CheckForNull FlowNode previous) {
return current instanceof BlockEndNode;
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ChunkFinder.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ChunkFinder.java
index 7a696cbb..37012494 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ChunkFinder.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ChunkFinder.java
@@ -2,17 +2,17 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Think of this as setting conditions to mark a region of interest in the graph of {@link FlowNode} from a {@link org.jenkinsci.plugins.workflow.flow.FlowExecution}.
* This is used to define a linear "chunk" from the graph of FlowNodes returned by a {@link ForkScanner}, after it applies ordering.
*
This is done by invoking {@link ForkScanner#visitSimpleChunks(SimpleChunkVisitor, ChunkFinder)}.
*
Your {@link SimpleChunkVisitor} will receive callbacks about chunk boundaries on the basis of the ChunkFinder.
- * It is responsible for tracking the state based on events fired
+ * It is responsible for tracking the state based on events fired.
*
- *
Common uses:
+ *
Common uses:
*
* - Find all {@link FlowNode}s within a specific block type, such the block created by a timeout block, 'node' (executor) block, etc
* - Find all {@link FlowNode}s between specific markers, such as labels, milestones, or steps generating an error
@@ -42,7 +42,7 @@ public interface ChunkFinder {
* @param previous Previous node, to use in testing chunk
* @return True if current node is the beginning of chunk
*/
- boolean isChunkStart(@Nonnull FlowNode current, @CheckForNull FlowNode previous);
+ boolean isChunkStart(@NonNull FlowNode current, @CheckForNull FlowNode previous);
/**
* Test if the current node is the end of a chunk (inclusive)
@@ -52,5 +52,5 @@ public interface ChunkFinder {
* @param previous Previous node, to use in testing chunk
* @return True if current is the end of a chunk (inclusive)
*/
- boolean isChunkEnd(@Nonnull FlowNode current, @CheckForNull FlowNode previous);
+ boolean isChunkEnd(@NonNull FlowNode current, @CheckForNull FlowNode previous);
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/DepthFirstScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/DepthFirstScanner.java
index 5677e2d0..ca85a8bf 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/DepthFirstScanner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/DepthFirstScanner.java
@@ -27,8 +27,8 @@
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.NotThreadSafe;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -65,7 +65,7 @@ protected void reset() {
}
@Override
- protected void setHeads(@Nonnull Collection heads) {
+ protected void setHeads(@NonNull Collection heads) {
if (heads.isEmpty()) {
return;
}
@@ -87,7 +87,7 @@ protected boolean testCandidate(FlowNode f, Collection blackList) {
}
@Override
- protected FlowNode next(@Nonnull FlowNode current, @Nonnull final Collection blackList) {
+ protected FlowNode next(@NonNull FlowNode current, @NonNull final Collection blackList) {
FlowNode output = null;
// Walk through parents of current node
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/Filterator.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/Filterator.java
index b77e40ae..a003e7b6 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/Filterator.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/Filterator.java
@@ -26,7 +26,7 @@
import com.google.common.base.Predicate;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Iterator;
/** Iterator that may be navigated through a filtered wrapper.
@@ -38,6 +38,6 @@
*/
public interface Filterator extends Iterator {
/** Returns a filtered view of the iterator, which calls the iterator until matches are found */
- @Nonnull
- Filterator filter(@Nonnull Predicate matchCondition);
+ @NonNull
+ Filterator filter(@NonNull Predicate matchCondition);
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FilteratorImpl.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FilteratorImpl.java
index b49ebf26..29f3e2b2 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FilteratorImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FilteratorImpl.java
@@ -26,8 +26,8 @@
import com.google.common.base.Predicate;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.NotThreadSafe;
import java.util.Iterator;
/** Filters an iterator against a match predicate by wrapping an iterator
@@ -40,12 +40,13 @@ class FilteratorImpl implements Filterator {
private Iterator wrapped = null;
private Predicate matchCondition = null;
+ @NonNull
@Override
- public FilteratorImpl filter(Predicate matchCondition) {
+ public FilteratorImpl filter(@NonNull Predicate matchCondition) {
return new FilteratorImpl<>(this, matchCondition);
}
- public FilteratorImpl(@Nonnull Iterator it, @Nonnull Predicate matchCondition) {
+ public FilteratorImpl(@NonNull Iterator it, @NonNull Predicate matchCondition) {
this.wrapped = it;
this.matchCondition = matchCondition;
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunk.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunk.java
index 6bce6d0c..254deda6 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunk.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunk.java
@@ -26,13 +26,13 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
- * Common container interface for a series of {@link FlowNode}s with a logical start and end.
- * We use this because every plugin has a different way of storing info about the nodes.
+ *
Common container interface for a series of {@link FlowNode}s with a logical start and end.
+ *
We use this because every plugin has a different way of storing info about the nodes.
*
- *
Common uses:
+ *
Common uses:
*
* - A single FlowNode (when coupling with timing/status APIs)
* - A block (with a {@link org.jenkinsci.plugins.workflow.graph.BlockStartNode} and {@link org.jenkinsci.plugins.workflow.graph.BlockEndNode})
@@ -45,9 +45,9 @@
* @author Sam Van Oort
*/
public interface FlowChunk {
- @Nonnull
+ @NonNull
FlowNode getFirstNode();
- @Nonnull
+ @NonNull
FlowNode getLastNode();
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunkWithContext.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunkWithContext.java
index 1285808b..f1ac1321 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunkWithContext.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowChunkWithContext.java
@@ -2,7 +2,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
/** FlowChunk with information about what comes before/after */
public interface FlowChunkWithContext extends FlowChunk {
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowNodeVisitor.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowNodeVisitor.java
index 790963e0..2362edfd 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowNodeVisitor.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowNodeVisitor.java
@@ -26,7 +26,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collection;
/**
@@ -42,5 +42,5 @@ public interface FlowNodeVisitor {
* @param f Node to visit
* @return False if we should stop visiting nodes
*/
- boolean visit(@Nonnull FlowNode f);
+ boolean visit(@NonNull FlowNode f);
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowScanningUtils.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowScanningUtils.java
index eb89b256..b9bc773c 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowScanningUtils.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/FlowScanningUtils.java
@@ -31,8 +31,8 @@
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Comparator;
import java.util.Iterator;
@@ -50,14 +50,9 @@ private FlowScanningUtils() {}
* @param actionClass Action class to look for
* @return Predicate that will match when FlowNode has the action given
*/
- @Nonnull
- public static Predicate hasActionPredicate(@Nonnull final Class extends Action> actionClass) {
- return new Predicate() {
- @Override
- public boolean apply(FlowNode input) {
- return (input != null && input.getAction(actionClass) != null);
- }
- };
+ @NonNull
+ public static Predicate hasActionPredicate(@NonNull final Class extends Action> actionClass) {
+ return input -> (input != null && input.getAction(actionClass) != null);
}
// Default predicates, which may be used for common conditions
@@ -119,9 +114,9 @@ public int compare(@CheckForNull FlowNode first, @CheckForNull FlowNode second)
* @param f {@link FlowNode} to start from.
* @return Iterator that returns all enclosing BlockStartNodes from the inside out.
*/
- @Nonnull
+ @NonNull
@Deprecated
- public static Filterator fetchEnclosingBlocks(@Nonnull FlowNode f) {
+ public static Filterator fetchEnclosingBlocks(@NonNull FlowNode f) {
return new FilteratorImpl<>((Iterator) f.iterateEnclosingBlocks().iterator(), Predicates.alwaysTrue());
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ForkScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ForkScanner.java
index b161deb1..82d5da13 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ForkScanner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/ForkScanner.java
@@ -34,10 +34,10 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import net.jcip.annotations.NotThreadSafe;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -122,11 +122,11 @@ public ForkScanner() {
}
- public ForkScanner(@Nonnull Collection heads) {
+ public ForkScanner(@NonNull Collection heads) {
this.setup(heads);
}
- public ForkScanner(@Nonnull Collection heads, @Nonnull Collection blackList) {
+ public ForkScanner(@NonNull Collection heads, @NonNull Collection blackList) {
this.setup(heads, blackList);
}
@@ -158,7 +158,7 @@ public boolean apply(@Nullable FlowNode input) {
* Specifically, requiring classes from workflow-cps to detect if something is a parallel step.
*/
@Deprecated
- public static void setParallelStartPredicate(@Nonnull Predicate pred) {
+ public static void setParallelStartPredicate(@NonNull Predicate pred) {
}
// Needed because the *next* node might be a parallel start if we start in middle and we don't know it
@@ -217,7 +217,7 @@ static class ParallelBlockStart {
BlockStartNode forkStart; // This is the node with child branches
ArrayDeque unvisited = new ArrayDeque<>(); // Remaining branches of this that we have have not visited yet
- ParallelBlockStart(@Nonnull BlockStartNode forkStart) {
+ ParallelBlockStart(@NonNull BlockStartNode forkStart) {
this.forkStart = forkStart;
}
@@ -251,7 +251,7 @@ public boolean isLeaf() {
* @throws IllegalStateException When you try to split a segment on a node that it doesn't contain, or invalid graph structure
* @return Recreated fork
*/
- Fork split(@Nonnull HashMap nodeMapping, @Nonnull BlockStartNode joinPoint, @Nonnull FlowPiece joiningBranch) {
+ Fork split(@NonNull HashMap nodeMapping, @NonNull BlockStartNode joinPoint, @NonNull FlowPiece joiningBranch) {
int index = visited.lastIndexOf(joinPoint); // Fork will be closer to end, so this is better than indexOf
Fork newFork = new Fork(joinPoint);
@@ -360,7 +360,7 @@ ArrayDeque convertForksToBlockStarts(ArrayDeque parall
* - Heads are all separate branches
*
*/
- ArrayDeque leastCommonAncestor(@Nonnull final Set heads) {
+ ArrayDeque leastCommonAncestor(@NonNull final Set heads) {
HashMap branches = new HashMap<>();
ArrayList> iterators = new ArrayList<>();
ArrayList livePieces = new ArrayList<>();
@@ -368,7 +368,7 @@ ArrayDeque leastCommonAncestor(@Nonnull final Set
ArrayDeque parallelForks = new ArrayDeque<>(); // Tracks the discovered forks in order of encounter
Predicate notAHead = new Predicate() { // Filter out pre-existing heads
- Collection checkHeads = convertToFastCheckable(heads);
+ final Collection checkHeads = convertToFastCheckable(heads);
@Override
public boolean apply(FlowNode input) { return !checkHeads.contains(input); }
@@ -439,18 +439,21 @@ ArrayDeque leastCommonAncestor(@Nonnull final Set
}
}
+ if (parallelForks.isEmpty()) {
+ throw new IllegalStateException("No least common ancestor found from " + heads);
+ }
+
// If we hit issues with the ordering of blocks by depth, apply a sorting to the parallels by depth
return convertForksToBlockStarts(parallelForks);
}
@Override
- protected void setHeads(@Nonnull Collection heads) {
+ protected void setHeads(@NonNull Collection heads) {
if (heads.size() > 1) {
for (FlowNode f : heads) {
headIds.add(f.getId());
}
parallelBlockStartStack = leastCommonAncestor(new LinkedHashSet<>(heads));
- assert parallelBlockStartStack.size() > 0;
currentParallelStart = parallelBlockStartStack.pop();
currentParallelStartNode = currentParallelStart.forkStart;
myCurrent = currentParallelStart.unvisited.pop();
@@ -566,7 +569,7 @@ public FlowNode next() {
@Override
@SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE",
justification = "Function call to modify state, special case where we don't need the returnVal")
- protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection blackList) {
+ protected FlowNode next(@NonNull FlowNode current, @NonNull Collection blackList) {
FlowNode output = null;
// First we look at the parents of the current node if present
@@ -619,13 +622,13 @@ protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection
return output;
}
- public static void visitSimpleChunks(@Nonnull Collection heads, @Nonnull Collection blacklist, @Nonnull SimpleChunkVisitor visitor, @Nonnull ChunkFinder finder) {
+ public static void visitSimpleChunks(@NonNull Collection heads, @NonNull Collection blacklist, @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder) {
ForkScanner scanner = new ForkScanner();
scanner.setup(heads, blacklist);
scanner.visitSimpleChunks(visitor, finder);
}
- public static void visitSimpleChunks(@Nonnull Collection heads, @Nonnull SimpleChunkVisitor visitor, @Nonnull ChunkFinder finder) {
+ public static void visitSimpleChunks(@NonNull Collection heads, @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder) {
ForkScanner scanner = new ForkScanner();
scanner.setup(heads);
scanner.visitSimpleChunks(visitor, finder);
@@ -638,7 +641,7 @@ public static void visitSimpleChunks(@Nonnull Collection heads, @Nonnu
* not just the last declared branch. (See issue JENKINS-38536)
*/
@CheckForNull
- static FlowNode findLastRunningNode(@Nonnull List candidates) {
+ static FlowNode findLastRunningNode(@NonNull List candidates) {
if (candidates.size() == 0) {
return null;
} else if (candidates.size() == 1) {
@@ -679,7 +682,7 @@ List currentParallelHeads() {
/** Pulls out firing the callbacks for parallels */
static void fireVisitParallelCallbacks(@CheckForNull FlowNode next, @CheckForNull FlowNode current, @CheckForNull FlowNode prev,
- @Nonnull SimpleChunkVisitor visitor, @Nonnull ChunkFinder finder, @Nonnull ForkScanner scanner) {
+ @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder, @NonNull ForkScanner scanner) {
// Trigger on parallels
switch (scanner.currentType) {
case NORMAL:
@@ -726,8 +729,8 @@ static void fireVisitParallelCallbacks(@CheckForNull FlowNode next, @CheckForNul
/** Abstracts out the simpleChunkVisitor callback-triggering logic.
* Note that a null value of "prev" is assumed to mean we're the last node. */
@SuppressFBWarnings(value="NP_LOAD_OF_KNOWN_NULL_VALUE", justification = "FindBugs doesn't like passing nulls to a method that can take null")
- static void fireVisitChunkCallbacks(@CheckForNull FlowNode next, @Nonnull FlowNode current, @CheckForNull FlowNode prev,
- @Nonnull SimpleChunkVisitor visitor, @Nonnull ChunkFinder finder, @Nonnull ForkScanner scanner) {
+ static void fireVisitChunkCallbacks(@CheckForNull FlowNode next, @NonNull FlowNode current, @CheckForNull FlowNode prev,
+ @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder, @NonNull ForkScanner scanner) {
boolean boundary = false;
if (prev == null && finder.isStartInsideChunk()) { // Last node, need to fire end event to start inside chunk
visitor.chunkEnd(current, prev, scanner);
@@ -751,7 +754,7 @@ static void fireVisitChunkCallbacks(@CheckForNull FlowNode next, @Nonnull FlowNo
}
/** Walk through flows */
- public void visitSimpleChunks(@Nonnull SimpleChunkVisitor visitor, @Nonnull ChunkFinder finder) {
+ public void visitSimpleChunks(@NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder) {
FlowNode prev;
if (this.currentParallelStart != null) {
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LabelledChunkFinder.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LabelledChunkFinder.java
index 7c8e6762..38b90fac 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LabelledChunkFinder.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LabelledChunkFinder.java
@@ -5,8 +5,8 @@
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Splits a flow execution into {@link FlowChunk}s whenever you have a label.
@@ -24,7 +24,7 @@ public boolean isStartInsideChunk() {
/** Start is anywhere with a {@link LabelAction} */
@Override
- public boolean isChunkStart(@Nonnull FlowNode current, @CheckForNull FlowNode previous) {
+ public boolean isChunkStart(@NonNull FlowNode current, @CheckForNull FlowNode previous) {
LabelAction la = current.getPersistentAction(LabelAction.class);
return la != null;
}
@@ -32,7 +32,7 @@ public boolean isChunkStart(@Nonnull FlowNode current, @CheckForNull FlowNode pr
/** End is where the previous node is a chunk start
* or this is a {@link BlockEndNode} whose {@link BlockStartNode} has a label action */
@Override
- public boolean isChunkEnd(@Nonnull FlowNode current, @CheckForNull FlowNode previous) {
+ public boolean isChunkEnd(@NonNull FlowNode current, @CheckForNull FlowNode previous) {
if (previous == null) {
return false;
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java
index 1618b748..970a0b54 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java
@@ -28,9 +28,9 @@
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.NotThreadSafe;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -70,7 +70,7 @@ public boolean setup(@CheckForNull Collection heads, @CheckForNull Col
}
@Override
- protected void setHeads(@Nonnull Collection heads) {
+ protected void setHeads(@NonNull Collection heads) {
Iterator it = heads.iterator();
if (it.hasNext()) {
this.myCurrent = jumpBlockScan(it.next(), myBlackList);
@@ -83,7 +83,7 @@ protected void setHeads(@Nonnull Collection heads) {
/** Keeps jumping over blocks until we hit the first node preceding a block */
@CheckForNull
- protected FlowNode jumpBlockScan(@CheckForNull FlowNode node, @Nonnull Collection blacklistNodes) {
+ protected FlowNode jumpBlockScan(@CheckForNull FlowNode node, @NonNull Collection blacklistNodes) {
FlowNode candidate = node;
// Find the first candidate node preceding a block... and filtering by blacklist
@@ -113,7 +113,7 @@ protected FlowNode jumpBlockScan(@CheckForNull FlowNode node, @Nonnull Collectio
}
@Override
- protected FlowNode next(@Nonnull FlowNode current, @Nonnull Collection blackList) {
+ protected FlowNode next(@CheckForNull FlowNode current, @NonNull Collection blackList) {
if (current == null) {
return null;
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearScanner.java
index 60e1e1e6..ca8ad4db 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearScanner.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearScanner.java
@@ -27,8 +27,9 @@
import com.google.common.base.Predicate;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.NotThreadSafe;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -46,7 +47,7 @@
* Use case: we don't care about parallel branches or know they don't exist, we just want to walk through the top-level blocks.
*
*
This is the fastest and simplest way to walk a flow, because you only care about a single node at a time.
- * Nuance: where there are multiple parent nodes (in a parallel block), and one is blacklisted, we'll find the first non-blacklisted one.
+ * Nuance: where there are multiple parent nodes (in a parallel block), and one is denylisted, we'll find the first non-denylisted one.
* @author Sam Van Oort
*/
@NotThreadSafe
@@ -63,10 +64,10 @@ protected void reset() {
/**
* {@inheritDoc}
- * @param heads Head nodes that have been filtered against blackList. Do not pass multiple heads.
+ * @param heads Head nodes that have been filtered against denyList. Do not pass multiple heads.
*/
@Override
- protected void setHeads(@Nonnull Collection heads) {
+ protected void setHeads(@NonNull Collection heads) {
Iterator it = heads.iterator();
if (it.hasNext()) {
this.myCurrent = it.next();
@@ -78,7 +79,7 @@ protected void setHeads(@Nonnull Collection heads) {
}
@Override
- protected FlowNode next(FlowNode current, @Nonnull Collection blackList) {
+ protected FlowNode next(@CheckForNull FlowNode current, @NonNull Collection blackList) {
if (current == null) {
return null;
}
@@ -98,8 +99,9 @@ protected FlowNode next(FlowNode current, @Nonnull Collection blackLis
* @deprecated prefer {@link #filteredNodes(FlowNode, Predicate)}
*/
@Deprecated
+ @NonNull
@Override
- public List filteredNodes(Collection heads, Predicate matchPredicate) {
+ public List filteredNodes(Collection heads, @NonNull Predicate matchPredicate) {
return super.filteredNodes(heads, matchPredicate);
}
@@ -109,6 +111,7 @@ public List filteredNodes(Collection heads, PredicateDo not pass multiple heads.
*/
+ @NonNull
@Override
public List