Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 29 additions & 85 deletions core/src/main/java/hudson/model/AbstractProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -1913,47 +1913,19 @@ public FormValidation doCheckAssignedLabelString(@AncestorInPath AbstractProject

public FormValidation doCheckLabel(@AncestorInPath AbstractProject<?,?> project,
@QueryParameter String value) {
return validateLabelExpression(value, project);
return LabelExpression.validate(value, project);
}

/**
* Validate label expression string.
*
* @param project May be specified to perform project specific validation.
* @since 1.590
* @deprecated Use {@link LabelExpression#validate(String, Item)} instead.
*/
@Deprecated
public static @NonNull FormValidation validateLabelExpression(String value, @CheckForNull AbstractProject<?, ?> project) {
if (Util.fixEmpty(value)==null)
return FormValidation.ok(); // nothing typed yet
try {
Label.parseExpression(value);
} catch (ANTLRException e) {
return FormValidation.error(e,
Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
}
Jenkins j = Jenkins.get();
Label l = j.getLabel(value);
if (l.isEmpty()) {
for (LabelAtom a : l.listAtoms()) {
if (a.isEmpty()) {
LabelAtom nearest = LabelAtom.findNearest(a.getName());
return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch_DidYouMean(a.getName(),nearest.getDisplayName()));
}
}
return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
}
if (project != null) {
for (AbstractProject.LabelValidator v : j
.getExtensionList(AbstractProject.LabelValidator.class)) {
FormValidation result = v.check(project, l);
if (!FormValidation.Kind.OK.equals(result.kind)) {
return result;
}
}
}
return FormValidation.okWithMarkup(Messages.AbstractProject_LabelLink(
j.getRootUrl(), Util.escape(l.getName()), l.getUrl(), l.getNodes().size(), l.getClouds().size())
);
return LabelExpression.validate(value, project);
}

public FormValidation doCheckCustomWorkspace(@QueryParameter String customWorkspace){
Expand Down Expand Up @@ -1985,64 +1957,12 @@ public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParamete
}

public AutoCompletionCandidates doAutoCompleteLabel(@QueryParameter String value) {
AutoCompletionCandidates c = new AutoCompletionCandidates();
Set<Label> labels = Jenkins.get().getLabels();
List<String> queries = new AutoCompleteSeeder(value).getSeeds();

for (String term : queries) {
for (Label l : labels) {
if (l.getName().startsWith(term)) {
c.add(l.getName());
}
}
}
return c;
return LabelExpression.autoComplete(value);
}

public List<SCMCheckoutStrategyDescriptor> getApplicableSCMCheckoutStrategyDescriptors(AbstractProject p) {
return SCMCheckoutStrategyDescriptor._for(p);
}

/**
* Utility class for taking the current input value and computing a list
* of potential terms to match against the list of defined labels.
*/
static class AutoCompleteSeeder {
private String source;

AutoCompleteSeeder(String source) {
this.source = source;
}

List<String> getSeeds() {
ArrayList<String> terms = new ArrayList<>();
boolean trailingQuote = source.endsWith("\"");
boolean leadingQuote = source.startsWith("\"");
boolean trailingSpace = source.endsWith(" ");

if (trailingQuote || (trailingSpace && !leadingQuote)) {
terms.add("");
} else {
if (leadingQuote) {
int quote = source.lastIndexOf('"');
if (quote == 0) {
terms.add(source.substring(1));
} else {
terms.add("");
}
} else {
int space = source.lastIndexOf(' ');
if (space > -1) {
terms.add(source.substring(space+1));
} else {
terms.add(source);
}
}
}

return terms;
}
}
}

/**
Expand Down Expand Up @@ -2128,8 +2048,11 @@ public void setCustomWorkspace(String customWorkspace) throws IOException {
* This extension point allows such restrictions.
*
* @since 1.540
* @deprecated Use {@link jenkins.model.labels.LabelValidator} instead.
*/
@Deprecated
public static abstract class LabelValidator implements ExtensionPoint {

/**
* Check the use of the label within the specified context.
*
Expand All @@ -2139,6 +2062,27 @@ public static abstract class LabelValidator implements ExtensionPoint {
*/
@NonNull
public abstract FormValidation check(@NonNull AbstractProject<?, ?> project, @NonNull Label label);

/**
* Validates the use of a label within a particular context.
* <p>
* This method exists to allow plugins to implement an override for it, enabling checking in non-AbstractProject
* contexts without needing to update their Jenkins dependency (and using the new LabelValidator instead).
*
* @param item The context item to be restricted by the label.
* @param label The label that the job wants to restrict itself to.
* @return The validation result.
*
* @since TODO
*/
@NonNull
public FormValidation checkItem(@NonNull Item item, @NonNull Label label) {
if (item instanceof AbstractProject<?, ?>) {
return this.check((AbstractProject<?, ?>) item, label);
}
return FormValidation.ok();
}

}

}
107 changes: 107 additions & 0 deletions core/src/main/java/hudson/model/labels/LabelExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@
*/
package hudson.model.labels;

import antlr.ANTLRException;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Util;
import hudson.model.AbstractProject;
import hudson.model.AutoCompletionCandidates;
import hudson.model.Item;
import hudson.model.Label;
import hudson.model.Messages;
import hudson.util.FormValidation;
import hudson.util.VariableResolver;
import java.util.List;
import java.util.Set;
import jenkins.model.Jenkins;
import jenkins.model.labels.LabelAutoCompleteSeeder;
import jenkins.model.labels.LabelValidator;

/**
* Boolean expression of labels.
Expand Down Expand Up @@ -210,4 +225,96 @@ public LabelOperatorPrecedence precedence() {
return LabelOperatorPrecedence.IMPLIES;
}
}

//region Auto-Completion and Validation

/**
* Generates auto-completion candidates for a (partial) label.
*
* @param label The (partial) label for which auto-completion is being requested.
* @return A set of auto-completion candidates.
* @since TODO
*/
@NonNull
public static AutoCompletionCandidates autoComplete(@Nullable String label) {
AutoCompletionCandidates c = new AutoCompletionCandidates();
Set<Label> labels = Jenkins.get().getLabels();
List<String> queries = new LabelAutoCompleteSeeder(Util.fixNull(label)).getSeeds();
for (String term : queries) {
for (Label l : labels) {
if (l.getName().startsWith(term)) {
c.add(l.getName());
}
}
}
return c;
}

/**
* Validates a label expression.
*
* @param expression The expression to validate.
* @return The validation result.
* @since TODO
*/
@NonNull
public static FormValidation validate(@Nullable String expression) {
return LabelExpression.validate(expression, null);
}

/**
* Validates a label expression.
*
* @param expression The label expression to validate.
* @param item The context item (like a job or a folder), if applicable; used for potential additional
* restrictions via {@link LabelValidator} instances.
* @return The validation result.
* @since TODO
*/
// FIXME: Should the messages be moved, or kept where they are for backward compatibility?
@NonNull
public static FormValidation validate(@Nullable String expression, @CheckForNull Item item) {
if (Util.fixEmptyAndTrim(expression) == null) {
return FormValidation.ok();
}
try {
Label.parseExpression(expression);
} catch (ANTLRException e) {
return FormValidation.error(e, Messages.LabelExpression_InvalidBooleanExpression(e.getMessage()));
}
final Jenkins j = Jenkins.get();
Label l = j.getLabel(expression);
if (l.isEmpty()) {
for (LabelAtom a : l.listAtoms()) {
if (a.isEmpty()) {
LabelAtom nearest = LabelAtom.findNearest(a.getName());
return FormValidation.warning(Messages.LabelExpression_NoMatch_DidYouMean(a.getName(),nearest.getDisplayName()));
}
}
return FormValidation.warning(Messages.LabelExpression_NoMatch());
}
if (item != null) {
// Use the project-oriented validators (including any that might implement checkItem to support non-Project
// items too).
// FIXME: Perhaps these should aggregate their errors/warnings?
for (AbstractProject.LabelValidator v : j.getExtensionList(AbstractProject.LabelValidator.class)) {
FormValidation result = v.checkItem(item, l);
if (!FormValidation.Kind.OK.equals(result.kind)) {
return result;
}
}
for (LabelValidator v : j.getExtensionList(LabelValidator.class)) {
FormValidation result = v.check(item, l);
if (!FormValidation.Kind.OK.equals(result.kind)) {
return result;
}
}
}
return FormValidation.okWithMarkup(Messages.LabelExpression_LabelLink(
j.getRootUrl(), Util.escape(l.getName()), l.getUrl(), l.getNodes().size(), l.getClouds().size())
);
}

//endregion

}
14 changes: 14 additions & 0 deletions core/src/main/java/hudson/tools/ToolInstallerDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
package hudson.tools;

import hudson.DescriptorExtensionList;
import hudson.model.AutoCompletionCandidates;
import hudson.model.Descriptor;
import hudson.model.labels.LabelExpression;
import hudson.util.FormValidation;
import jenkins.model.Jenkins;

import java.util.List;
import java.util.ArrayList;
import org.kohsuke.stapler.QueryParameter;

/**
* Descriptor for a {@link ToolInstaller}.
Expand Down Expand Up @@ -62,4 +66,14 @@ public static List<ToolInstallerDescriptor<?>> for_(Class<? extends ToolInstalla
return r;
}

@SuppressWarnings("unused")
public AutoCompletionCandidates doAutoCompleteLabel(@QueryParameter String value) {
return LabelExpression.autoComplete(value);
}

@SuppressWarnings("unused")
public FormValidation doCheckLabel(@QueryParameter String value) {
return LabelExpression.validate(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package jenkins.model.labels;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.ArrayList;
import java.util.List;

/**
* Utility class for taking the current input value and computing a list of potential terms to match against the
* list of defined labels.
*
* @since TODO
*/
public class LabelAutoCompleteSeeder {

private final String source;

/**
* Creates a new auto-complete seeder for labels.
*
* @param source The (partial) label expression to use as the source..
*/
public LabelAutoCompleteSeeder(@NonNull String source) {
this.source = source;
}

/**
* Gets a list of seeds for label auto-completion.
*
* @return A list of seeds for label auto-completion.
*/
@NonNull
public List<String> getSeeds() {
final ArrayList<String> terms = new ArrayList<>();
boolean trailingQuote = source.endsWith("\"");
boolean leadingQuote = source.startsWith("\"");
boolean trailingSpace = source.endsWith(" ");
if (trailingQuote || (trailingSpace && !leadingQuote)) {
terms.add("");
} else {
if (leadingQuote) {
int quote = source.lastIndexOf('"');
if (quote == 0) {
terms.add(source.substring(1));
} else {
terms.add("");
}
} else {
int space = source.lastIndexOf(' ');
if (space > -1) {
terms.add(source.substring(space+1));
} else {
terms.add(source);
}
}
}
return terms;
}

}
27 changes: 27 additions & 0 deletions core/src/main/java/jenkins/model/labels/LabelValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package jenkins.model.labels;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionPoint;
import hudson.model.Item;
import hudson.model.Label;
import hudson.util.FormValidation;

/**
* Plugins may want to contribute additional restrictions on the use of specific labels for specific context items.
* This extension point allows such restrictions.
*
* @since TODO
*/
public interface LabelValidator extends ExtensionPoint {

/**
* Validates the use of a label within a particular context.
*
* @param item The context item to be restricted by the label.
* @param label The label that the job wants to restrict itself to.
* @return The validation result.
*/
@NonNull
FormValidation check(@NonNull Item item, @NonNull Label label);

}
Loading