diff --git a/bom/pom.xml b/bom/pom.xml
index 961dd434c7e7..e187ab08f7ea 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -68,11 +68,6 @@ THE SOFTWARE.
pom
import
-
- antlr
- antlr
- 2.7.7
-
args4j
@@ -190,6 +185,11 @@ THE SOFTWARE.
kxml2
2.3.0
+
+ org.antlr
+ antlr4-runtime
+ ${antlr.version}
+
org.apache.ant
ant
diff --git a/core/pom.xml b/core/pom.xml
index 69b6a167f479..be76f35c8bbe 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -67,10 +67,6 @@ THE SOFTWARE.
${project.groupId}
remoting
-
- antlr
- antlr
-
args4j
args4j
@@ -278,6 +274,10 @@ THE SOFTWARE.
net.sf.kxml
kxml2
+
+ org.antlr
+ antlr4-runtime
+
org.apache.ant
ant
@@ -571,7 +571,6 @@ THE SOFTWARE.
generate-sources
- ${project.build.directory}/generated-sources/antlr
${project.build.directory}/generated-sources/localizer
${project.build.directory}/generated-sources/taglib-interface
@@ -632,29 +631,15 @@ THE SOFTWARE.
- org.codehaus.mojo
- antlr-maven-plugin
+ org.antlr
+ antlr4-maven-plugin
- cron
+ antlr
- generate
+ antlr4
-
- ${basedir}/src/main/grammar
- crontab.g
-
-
-
- labelExpr
-
- generate
-
-
- ${basedir}/src/main/grammar
- labelExpr.g
-
diff --git a/core/src/main/antlr4/hudson/model/labels/LabelExpressionLexer.g4 b/core/src/main/antlr4/hudson/model/labels/LabelExpressionLexer.g4
new file mode 100644
index 000000000000..45e8ca01b233
--- /dev/null
+++ b/core/src/main/antlr4/hudson/model/labels/LabelExpressionLexer.g4
@@ -0,0 +1,57 @@
+lexer grammar LabelExpressionLexer;
+
+AND
+ : '&&'
+ ;
+
+OR
+ : '||'
+ ;
+
+NOT
+ : '!'
+ ;
+
+IMPLIES
+ : '->'
+ ;
+
+IFF
+ : '<->'
+ ;
+
+LPAREN
+ : '('
+ ;
+
+RPAREN
+ : ')'
+ ;
+
+fragment IDENTIFIER_PART
+ : ~ ('&' | '|' | '!' | '<' | '>' | '(' | ')' | ' ' | '\t' | '"' | '\'' | '-')
+ ;
+
+ATOM
+/*
+ the real check of valid identifier happens in LabelAtom.get()
+
+ https://www.antlr2.org/doc/lexer.html#usingexplicit
+ If we are seeing currently a '-', we check that the next char is not a '>' which will be a IMPLIES.
+ Otherwise the ATOM and the IMPLIES will collide and expr like a->b will just be parsed as ATOM (without spaces)
+*/
+
+ : (
+ { _input.LA(2) != '>' }? '-' | IDENTIFIER_PART)+
+ ;
+
+WS
+ : (' ' | '\t')+ -> skip
+ ;
+
+STRINGLITERAL
+ : '"' ('\\' ('b' | 't' | 'n' | 'f' | 'r' | '"' | '\'' | '\\') /* escape */
+
+ | ~ ('\\' | '"' | '\r' | '\n'))* '"'
+ ;
+
diff --git a/core/src/main/antlr4/hudson/model/labels/LabelExpressionParser.g4 b/core/src/main/antlr4/hudson/model/labels/LabelExpressionParser.g4
new file mode 100644
index 000000000000..2ac7f2fedb51
--- /dev/null
+++ b/core/src/main/antlr4/hudson/model/labels/LabelExpressionParser.g4
@@ -0,0 +1,56 @@
+parser grammar LabelExpressionParser;
+
+@ header
+{
+import hudson.model.Label;
+}
+
+options { tokenVocab = LabelExpressionLexer; }
+// order of precedence is as per http://en.wikipedia.org/wiki/Logical_connective#Order_of_precedence
+
+expr returns[Label l]
+ : term1
+ { $l=$term1.ctx.l; } EOF
+ ;
+
+term1 returns[Label l] locals[Label r]
+ : term2
+ { $l=$term2.ctx.l; } (IFF term2
+ { $r=$term2.ctx.l; $l=$l.iff($r); })*
+ ;
+ // (a->b)->c != a->(b->c) (for example in case of a=F,b=T,c=F) so don't allow chaining
+
+term2 returns[Label l] locals[Label r]
+ : term3
+ { $l=$term3.ctx.l; } (IMPLIES term3
+ { $r=$term3.ctx.l; $l=$l.implies($r); })?
+ ;
+
+term3 returns[Label l] locals[Label r]
+ : term4
+ { $l=$term4.ctx.l; } (OR term4
+ { $r=$term4.ctx.l; $l=$l.or($r); })*
+ ;
+
+term4 returns[Label l] locals[Label r]
+ : term5
+ { $l=$term5.ctx.l; } (AND term5
+ { $r=$term5.ctx.l; $l=$l.and($r); })*
+ ;
+
+term5 returns[Label l]
+ : term6
+ { $l=$term6.ctx.l; }
+ | NOT term6
+ { $l=$term6.ctx.l; $l=$l.not(); }
+ ;
+
+term6 returns[Label l]
+ : LPAREN term1 RPAREN
+ { $l=$term1.ctx.l ; $l=$l.paren(); }
+ | ATOM
+ { $l=LabelAtom.get($ATOM.getText()); }
+ | STRINGLITERAL
+ { $l=LabelAtom.get(hudson.util.QuotedStringTokenizer.unquote($STRINGLITERAL.getText())); }
+ ;
+
diff --git a/core/src/main/antlr4/hudson/scheduler/CrontabLexer.g4 b/core/src/main/antlr4/hudson/scheduler/CrontabLexer.g4
new file mode 100644
index 000000000000..a8e471c4c8f1
--- /dev/null
+++ b/core/src/main/antlr4/hudson/scheduler/CrontabLexer.g4
@@ -0,0 +1,70 @@
+lexer grammar CrontabLexer;
+
+TOKEN
+ : ('0' .. '9')+
+ ;
+
+WS
+ : (' ' | '\t')+
+ ;
+
+MINUS
+ : '-'
+ ;
+
+STAR
+ : '*'
+ ;
+
+DIV
+ : '/'
+ ;
+
+OR
+ : ','
+ ;
+
+AT
+ : '@'
+ ;
+
+H
+ : 'H'
+ ;
+
+LPAREN
+ : '('
+ ;
+
+RPAREN
+ : ')'
+ ;
+
+YEARLY
+ : 'yearly'
+ ;
+
+ANNUALLY
+ : 'annually'
+ ;
+
+MONTHLY
+ : 'monthly'
+ ;
+
+WEEKLY
+ : 'weekly'
+ ;
+
+DAILY
+ : 'daily'
+ ;
+
+MIDNIGHT
+ : 'midnight'
+ ;
+
+HOURLY
+ : 'hourly'
+ ;
+
diff --git a/core/src/main/antlr4/hudson/scheduler/CrontabParser.g4 b/core/src/main/antlr4/hudson/scheduler/CrontabParser.g4
new file mode 100644
index 000000000000..4495afa62a34
--- /dev/null
+++ b/core/src/main/antlr4/hudson/scheduler/CrontabParser.g4
@@ -0,0 +1,83 @@
+parser grammar CrontabParser;
+
+
+options { tokenVocab = CrontabLexer; superClass = BaseParser; }
+startRule[CronTab table]
+ : expr[0]
+ { $table.bits[0]=$expr.ctx.bits; } WS expr[1]
+ { $table.bits[1]=$expr.ctx.bits; } WS expr[2]
+ { $table.bits[2]=$expr.ctx.bits; } WS expr[3]
+ { $table.bits[3]=$expr.ctx.bits; } WS expr[4]
+ { $table.dayOfWeek=(int)$expr.ctx.bits; } EOF
+ | (AT ('yearly'
+ {
+ $table.set("H H H H *",getHashForTokens());
+ } | 'annually'
+ {
+ $table.set("H H H H *",getHashForTokens());
+ } | 'monthly'
+ {
+ $table.set("H H H * *",getHashForTokens());
+ } | 'weekly'
+ {
+ $table.set("H H * * H",getHashForTokens());
+ } | 'daily'
+ {
+ $table.set("H H * * *",getHashForTokens());
+ } | 'midnight'
+ {
+ $table.set("H H(0-2) * * *",getHashForTokens());
+ } | 'hourly'
+ {
+ $table.set("H * * * *",getHashForTokens());
+ }))
+ ;
+
+expr[int field] returns[long bits=0] locals[long lhs, long rhs=0]
+ : term[field]
+ { $lhs = $term.ctx.bits; } (OR expr[field]
+ { $rhs = $expr.ctx.bits; })?
+ {
+ $bits = $lhs|$rhs;
+ }
+ ;
+
+term[int field] returns[long bits=0] locals[int d=NO_STEP, int s, int e]
+ : token
+ { $s=$token.ctx.value; } MINUS token
+ { $e=$token.ctx.value; } (DIV token
+ { $d=$token.ctx.value; })?
+ {
+ $bits = doRange($s,$e,$d,$field);
+ }
+ | token
+ {
+ rangeCheck($token.ctx.value,$field);
+ $bits = 1L<<$token.ctx.value;
+ }
+ | STAR (DIV token
+ { $d=$token.ctx.value; })?
+ {
+ $bits = doRange($d,$field);
+ }
+ | 'H' LPAREN token
+ { $s=$token.ctx.value; } MINUS token
+ { $e=$token.ctx.value; } RPAREN (DIV token
+ { $d=$token.ctx.value; })?
+ {
+ $bits = doHash($s,$e,$d,$field);
+ }
+ | 'H' (DIV token
+ { $d=$token.ctx.value; })?
+ {
+ $bits = doHash($d,$field);
+ }
+ ;
+
+token returns[int value=0]
+ : TOKEN
+ {
+ $value = Integer.parseInt($TOKEN.getText());
+ }
+ ;
+
diff --git a/core/src/main/grammar/crontab.g b/core/src/main/grammar/crontab.g
deleted file mode 100644
index 4ace2d3adf9c..000000000000
--- a/core/src/main/grammar/crontab.g
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-header {
- package hudson.scheduler;
-}
-
-class CrontabParser extends Parser("BaseParser");
-options {
- defaultErrorHandler=false;
-}
-
-startRule [CronTab table]
-throws ANTLRException
-{
- long m,h,d,mnth,dow;
-}
- : m=expr[0] WS h=expr[1] WS d=expr[2] WS mnth=expr[3] WS dow=expr[4] EOF
- {
- table.bits[0]=m;
- table.bits[1]=h;
- table.bits[2]=d;
- table.bits[3]=mnth;
- table.dayOfWeek=(int)dow;
- }
- | ( AT
- (
- "yearly"
- {
- table.set("H H H H *",getHashForTokens());
- }
- | "annually"
- {
- table.set("H H H H *",getHashForTokens());
- }
- | "monthly"
- {
- table.set("H H H * *",getHashForTokens());
- }
- | "weekly"
- {
- table.set("H H * * H",getHashForTokens());
- }
- | "daily"
- {
- table.set("H H * * *",getHashForTokens());
- }
- | "midnight"
- {
- table.set("H H(0-2) * * *",getHashForTokens());
- }
- | "hourly"
- {
- table.set("H * * * *",getHashForTokens());
- }
- )
- )
- ;
-
-expr [int field]
-returns [long bits=0]
-throws ANTLRException
-{
- long lhs,rhs=0;
-}
- : lhs=term[field] ("," rhs=expr[field])?
- {
- bits = lhs|rhs;
- }
- ;
-
-term [int field]
-returns [long bits=0]
-throws ANTLRException
-{
- int d=NO_STEP,s,e,t;
-}
- : (token "-")=> s=token "-" e=token ( "/" d=token )?
- {
- bits = doRange(s,e,d,field);
- }
- | t=token
- {
- rangeCheck(t,field);
- bits = 1L< "H" "(" s=token "-" e=token ")" ( "/" d=token )?
- {
- bits = doHash(s,e,d,field);
- }
- | "H" ( "/" d=token )?
- {
- bits = doHash(d,field);
- }
- ;
-
-token
-returns [int value=0]
- : t:TOKEN
- {
- value = Integer.parseInt(t.getText());
- }
- ;
-
-class CrontabLexer extends Lexer;
-options {
- k=2; // I'm sure there's a better way to do this than using lookahead. ANTLR sucks...
- defaultErrorHandler=false;
-}
-
-TOKEN
-options {
- paraphrase="a number";
-}
- : ('0'..'9')+
- ;
-
-WS
-options {
- paraphrase="space";
-}
- : (' '|'\t')+
- ;
-
-MINUS: '-';
-STAR: '*';
-DIV: '/';
-OR: ',';
-AT: '@';
-H: 'H';
-LPAREN: '(';
-RPAREN: ')';
-
-YEARLY: "yearly";
-ANNUALLY: "annually";
-MONTHLY: "monthly";
-WEEKLY: "weekly";
-DAILY: "daily";
-MIDNIGHT: "midnight";
-HOURLY: "hourly";
diff --git a/core/src/main/grammar/labelExpr.g b/core/src/main/grammar/labelExpr.g
deleted file mode 100644
index c100f9e11cd9..000000000000
--- a/core/src/main/grammar/labelExpr.g
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2010, InfraDNA, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-header {
- package hudson.model.labels;
- import hudson.model.Label;
-}
-
-class LabelExpressionParser extends Parser;
-options {
- defaultErrorHandler=false;
-}
-
-// order of precedence is as per http://en.wikipedia.org/wiki/Logical_connective#Order_of_precedence
-
-expr
-returns [Label l]
- : l=term1 EOF
- ;
-
-term1
-returns [Label l]
-{ Label r; }
- : l=term2( IFF r=term2 {l=l.iff(r);} )*
- ;
-
-// (a->b)->c != a->(b->c) (for example in case of a=F,b=T,c=F) so don't allow chaining
-term2
-returns [Label l]
-{ Label r; }
- : l=term3( IMPLIES r=term3 {l=l.implies(r);} )?
- ;
-
-term3
-returns [Label l]
-{ Label r; }
- : l=term4 ( OR r=term4 {l=l.or(r);} )*
- ;
-
-term4
-returns [Label l]
-{ Label r; }
- : l=term5 ( AND r=term5 {l=l.and(r);} )*
- ;
-
-term5
-returns [Label l]
-{ Label x; }
- : l=term6
- | NOT x=term6
- { l=x.not(); }
- ;
-
-term6
-returns [Label l]
-options { generateAmbigWarnings=false; }
- : LPAREN l=term1 RPAREN
- { l=l.paren(); }
- | a:ATOM
- { l=LabelAtom.get(a.getText()); }
- | s:STRINGLITERAL
- { l=LabelAtom.get(hudson.util.QuotedStringTokenizer.unquote(s.getText())); }
- ;
-
-class LabelExpressionLexer extends Lexer;
-options {
- // There must be an options section to satisfy
- // org.codehaus.mojo.antlr.metadata.MetadataExtracter#intrepret, even if it is empty.
-
- // https://www.antlr2.org/doc/lexer.html#Common_prefixes
- // to prevent nondeterminism between IMPLIES and ATOM related to the first "-"
- k=2;
-}
-
-AND: "&&";
-OR: "||";
-NOT: "!";
-IMPLIES:"->";
-IFF: "<->";
-LPAREN: "(";
-RPAREN: ")";
-
-protected
-IDENTIFIER_PART
- : ~( '&' | '|' | '!' | '<' | '>' | '(' | ')' | ' ' | '\t' | '\"' | '\'' | '-' )
- ;
-
-ATOM
-/*
- the real check of valid identifier happens in LabelAtom.get()
-
- https://www.antlr2.org/doc/lexer.html#usingexplicit
- If we are seeing currently a '-', we check that the next char is not a '>' which will be a IMPLIES.
- Otherwise the ATOM and the IMPLIES will collide and expr like a->b will just be parsed as ATOM (without spaces)
-*/
- : (
- { LA(2) != '>' }? '-'
- | IDENTIFIER_PART
- )+
- ;
-
-WS
- : (' '|'\t')+
- { $setType(Token.SKIP); }
- ;
-
-STRINGLITERAL
- : '"'
- ( '\\' ( 'b' | 't' | 'n' | 'f' | 'r' | '\"' | '\'' | '\\' ) /* escape */
- | ~( '\\' | '"' | '\r' | '\n' )
- )*
- '"'
- ;
diff --git a/core/src/main/java/antlr/ANTLRException.java b/core/src/main/java/antlr/ANTLRException.java
new file mode 100644
index 000000000000..77447338a64b
--- /dev/null
+++ b/core/src/main/java/antlr/ANTLRException.java
@@ -0,0 +1,21 @@
+package antlr;
+
+/**
+ * This class is for binary compatibility for older plugins that import {@link ANTLRException}.
+ *
+ * @deprecated use {@link IllegalArgumentException}
+ */
+@Deprecated
+public class ANTLRException extends IllegalArgumentException {
+ public ANTLRException(String message) {
+ super(message);
+ }
+
+ public ANTLRException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ANTLRException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index d3a1ea34119d..fa1a7777df7b 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -31,7 +31,6 @@
import static hudson.scm.PollingResult.BUILD_NOW;
import static hudson.scm.PollingResult.NO_CHANGES;
-import antlr.ANTLRException;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -414,7 +413,7 @@ public String getAssignedLabelString() {
try {
Label.parseExpression(assignedNode);
return assignedNode;
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
// must be old label or host name that includes whitespace or other unsafe chars
return LabelAtom.escape(assignedNode);
}
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index c285a1a28fda..43604d408aac 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -26,7 +26,6 @@
import static hudson.Util.fixNull;
-import antlr.ANTLRException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
@@ -56,7 +55,6 @@
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import java.io.Serializable;
-import java.io.StringReader;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -68,6 +66,9 @@
import java.util.stream.StreamSupport;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
+import jenkins.util.antlr.JenkinsANTLRErrorListener;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.StaplerRequest;
@@ -610,10 +611,18 @@ public static Label get(String l) {
* Parses the expression into a label expression tree.
*
* TODO: replace this with a real parser later
- */
- public static Label parseExpression(@NonNull String labelExpression) throws ANTLRException {
- LabelExpressionLexer lexer = new LabelExpressionLexer(new StringReader(labelExpression));
- return new LabelExpressionParser(lexer).expr();
+ *
+ * @param labelExpression the label expression to be parsed
+ * @throws IllegalArgumentException if the label expression cannot be parsed
+ */
+ public static Label parseExpression(@NonNull String labelExpression) {
+ LabelExpressionLexer lexer = new LabelExpressionLexer(CharStreams.fromString(labelExpression));
+ lexer.removeErrorListeners();
+ lexer.addErrorListener(new JenkinsANTLRErrorListener());
+ LabelExpressionParser parser = new LabelExpressionParser(new CommonTokenStream(lexer));
+ parser.removeErrorListeners();
+ parser.addErrorListener(new JenkinsANTLRErrorListener());
+ return parser.expr().l;
}
/**
diff --git a/core/src/main/java/hudson/model/labels/LabelExpression.java b/core/src/main/java/hudson/model/labels/LabelExpression.java
index 40a7fc6f80db..20e666963883 100644
--- a/core/src/main/java/hudson/model/labels/LabelExpression.java
+++ b/core/src/main/java/hudson/model/labels/LabelExpression.java
@@ -24,7 +24,6 @@
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;
@@ -282,7 +281,7 @@ public static FormValidation validate(@Nullable String expression, @CheckForNull
}
try {
Label.parseExpression(expression);
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
return FormValidation.error(e, Messages.LabelExpression_InvalidBooleanExpression(e.getMessage()));
}
final Jenkins j = Jenkins.get();
diff --git a/core/src/main/java/hudson/scheduler/BaseParser.java b/core/src/main/java/hudson/scheduler/BaseParser.java
index 36a713101177..2c386cb3bdbc 100644
--- a/core/src/main/java/hudson/scheduler/BaseParser.java
+++ b/core/src/main/java/hudson/scheduler/BaseParser.java
@@ -24,20 +24,15 @@
package hudson.scheduler;
-import antlr.ANTLRException;
-import antlr.LLkParser;
-import antlr.ParserSharedInputState;
-import antlr.SemanticException;
-import antlr.Token;
-import antlr.TokenBuffer;
-import antlr.TokenStream;
-import antlr.TokenStreamException;
import jenkins.util.SystemProperties;
+import org.antlr.v4.runtime.InputMismatchException;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.TokenStream;
/**
* @author Kohsuke Kawaguchi
*/
-abstract class BaseParser extends LLkParser {
+abstract class BaseParser extends Parser {
// lower/upper bounds of fields (inclusive)
static final int[] LOWER_BOUNDS = new int[] {0, 0, 1, 1, 0};
static final int[] UPPER_BOUNDS = new int[] {59, 23, 31, 12, 7};
@@ -47,28 +42,29 @@ abstract class BaseParser extends LLkParser {
*/
protected Hash hash = Hash.zero();
- protected BaseParser(int i) {
- super(i);
- }
+ /**
+ * Custom error message overriding ANTLR's {@link InputMismatchException}
+ */
+ private String errorMessage;
- protected BaseParser(ParserSharedInputState parserSharedInputState, int i) {
- super(parserSharedInputState, i);
+ BaseParser(TokenStream input) {
+ super(input);
}
- protected BaseParser(TokenBuffer tokenBuffer, int i) {
- super(tokenBuffer, i);
+ public void setHash(Hash hash) {
+ if (hash == null) hash = Hash.zero();
+ this.hash = hash;
}
- protected BaseParser(TokenStream tokenStream, int i) {
- super(tokenStream, i);
+ public String getErrorMessage() {
+ return errorMessage;
}
- public void setHash(Hash hash) {
- if (hash == null) hash = Hash.zero();
- this.hash = hash;
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
}
- protected long doRange(int start, int end, int step, int field) throws ANTLRException {
+ protected long doRange(int start, int end, int step, int field) {
rangeCheck(start, field);
rangeCheck(end, field);
if (step <= 0)
@@ -83,7 +79,7 @@ protected long doRange(int start, int end, int step, int field) throws ANTLRExce
return bits;
}
- protected long doRange(int step, int field) throws ANTLRException {
+ protected long doRange(int step, int field) {
return doRange(LOWER_BOUNDS[field], UPPER_BOUNDS[field], step, field);
}
@@ -94,14 +90,14 @@ protected long doRange(int step, int field) throws ANTLRException {
* Increments. For example, 15 if "H/15". Or {@link #NO_STEP} to indicate
* the special constant for "H" without the step value.
*/
- protected long doHash(int step, int field) throws ANTLRException {
+ protected long doHash(int step, int field) {
int u = UPPER_BOUNDS[field];
if (field == 2) u = 28; // day of month can vary depending on month, so to make life simpler, just use [1,28] that's always safe
if (field == 4) u = 6; // Both 0 and 7 of day of week are Sunday. For better distribution, limit upper bound to 6
return doHash(LOWER_BOUNDS[field], u, step, field);
}
- protected long doHash(int s, int e, int step, int field) throws ANTLRException {
+ protected long doHash(int s, int e, int step, int field) {
rangeCheck(s, field);
rangeCheck(e, field);
if (step > e - s + 1) {
@@ -124,20 +120,15 @@ protected long doHash(int s, int e, int step, int field) throws ANTLRException {
}
}
- protected void rangeCheck(int value, int field) throws ANTLRException {
+ protected void rangeCheck(int value, int field) {
if (value < LOWER_BOUNDS[field] || UPPER_BOUNDS[field] < value) {
error(Messages.BaseParser_OutOfRange(value, LOWER_BOUNDS[field], UPPER_BOUNDS[field]));
}
}
- private void error(String msg) throws TokenStreamException, SemanticException {
- Token token = LT(0);
- throw new SemanticException(
- msg,
- token.getFilename(),
- token.getLine(),
- token.getColumn()
- );
+ private void error(String msg) {
+ setErrorMessage(msg);
+ throw new InputMismatchException(this);
}
protected Hash getHashForTokens() {
diff --git a/core/src/main/java/hudson/scheduler/CronTab.java b/core/src/main/java/hudson/scheduler/CronTab.java
index 193f3a67944b..2c4b188a0de2 100644
--- a/core/src/main/java/hudson/scheduler/CronTab.java
+++ b/core/src/main/java/hudson/scheduler/CronTab.java
@@ -29,15 +29,16 @@
import static java.util.Calendar.MINUTE;
import static java.util.Calendar.MONTH;
-import antlr.ANTLRException;
import edu.umd.cs.findbugs.annotations.CheckForNull;
-import java.io.StringReader;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import jenkins.util.antlr.JenkinsANTLRErrorListener;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
/**
* Table for driving scheduled tasks.
@@ -67,53 +68,70 @@ public final class CronTab {
*/
private @CheckForNull String specTimezone;
- public CronTab(String format) throws ANTLRException {
+ /**
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ */
+ public CronTab(String format) {
this(format, null);
}
- public CronTab(String format, Hash hash) throws ANTLRException {
+ /**
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ */
+ public CronTab(String format, Hash hash) {
this(format, 1, hash);
}
/**
- * @deprecated as of 1.448
- * Use {@link #CronTab(String, int, Hash)}
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ * @deprecated use {@link #CronTab(String, int, Hash)}
*/
- @Deprecated
- public CronTab(String format, int line) throws ANTLRException {
+ @Deprecated(since = "1.448")
+ public CronTab(String format, int line) {
set(format, line, null);
}
/**
+ * @param format the crontab entry to be parsed
* @param hash
* Used to spread out token like "@daily". Null to preserve the legacy behaviour
* of not spreading it out at all.
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
*/
- public CronTab(String format, int line, Hash hash) throws ANTLRException {
+ public CronTab(String format, int line, Hash hash) {
this(format, line, hash, null);
}
/**
+ * @param format the crontab entry to be parsed
* @param timezone
* Used to schedule cron in a different timezone. Null to use the default system
* timezone
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
* @since 1.615
*/
- public CronTab(String format, int line, Hash hash, @CheckForNull String timezone) throws ANTLRException {
+ public CronTab(String format, int line, Hash hash, @CheckForNull String timezone) {
set(format, line, hash, timezone);
}
- private void set(String format, int line, Hash hash) throws ANTLRException {
+ private void set(String format, int line, Hash hash) {
set(format, line, hash, null);
}
/**
* @since 1.615
*/
- private void set(String format, int line, Hash hash, String timezone) throws ANTLRException {
- CrontabLexer lexer = new CrontabLexer(new StringReader(format));
+ private void set(String format, int line, Hash hash, String timezone) {
+ CrontabLexer lexer = new CrontabLexer(CharStreams.fromString(format));
+ lexer.removeErrorListeners();
+ lexer.addErrorListener(new JenkinsANTLRErrorListener());
lexer.setLine(line);
- CrontabParser parser = new CrontabParser(lexer);
+ CrontabParser parser = new CrontabParser(new CommonTokenStream(lexer));
+ parser.removeErrorListeners();
+ parser.addErrorListener(new JenkinsANTLRErrorListener(parser::getErrorMessage));
parser.setHash(hash);
spec = format;
specTimezone = timezone;
@@ -465,7 +483,11 @@ public Calendar floor(Calendar cal) {
}
}
- void set(String format, Hash hash) throws ANTLRException {
+ /**
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ */
+ void set(String format, Hash hash) {
set(format, 1, hash);
}
diff --git a/core/src/main/java/hudson/scheduler/CronTabList.java b/core/src/main/java/hudson/scheduler/CronTabList.java
index 7a1b4760ff75..f9be65fe12d9 100644
--- a/core/src/main/java/hudson/scheduler/CronTabList.java
+++ b/core/src/main/java/hudson/scheduler/CronTabList.java
@@ -91,11 +91,19 @@ public String checkSanity() {
return null;
}
- public static CronTabList create(@NonNull String format) throws ANTLRException {
+ /**
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ */
+ public static CronTabList create(@NonNull String format) {
return create(format, null);
}
- public static CronTabList create(@NonNull String format, Hash hash) throws ANTLRException {
+ /**
+ * @param format the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
+ */
+ public static CronTabList create(@NonNull String format, Hash hash) {
Vector r = new Vector<>();
int lineNumber = 0;
String timezone = null;
@@ -119,8 +127,8 @@ public static CronTabList create(@NonNull String format, Hash hash) throws ANTLR
continue; // ignorable line
try {
r.add(new CronTab(line, lineNumber, hash, timezone));
- } catch (ANTLRException e) {
- throw new ANTLRException(Messages.CronTabList_InvalidInput(line, e.toString()), e);
+ } catch (IllegalArgumentException e) {
+ throw new ANTLRException(Messages.CronTabList_InvalidInput(line, e.getMessage()), e);
}
}
diff --git a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
index adabf6e07dc3..6f09e8109973 100644
--- a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
@@ -27,7 +27,6 @@
import static hudson.Util.fixNull;
import static java.util.logging.Level.INFO;
-import antlr.ANTLRException;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Computer;
@@ -67,9 +66,12 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy {
private boolean ignorePostCommitHooks;
@DataBoundConstructor
- public SCMTrigger(String scmpoll_spec) throws ANTLRException {
+ public SCMTrigger(String scmpoll_spec) {
super(scmpoll_spec);
}
@@ -125,7 +124,7 @@ public SCMTrigger(String scmpoll_spec) throws ANTLRException {
* @deprecated since 2.21
*/
@Deprecated
- public SCMTrigger(String scmpoll_spec, boolean ignorePostCommitHooks) throws ANTLRException {
+ public SCMTrigger(String scmpoll_spec, boolean ignorePostCommitHooks) {
super(scmpoll_spec);
this.ignorePostCommitHooks = ignorePostCommitHooks;
}
diff --git a/core/src/main/java/hudson/triggers/TimerTrigger.java b/core/src/main/java/hudson/triggers/TimerTrigger.java
index 054e3e63334f..bed439528659 100644
--- a/core/src/main/java/hudson/triggers/TimerTrigger.java
+++ b/core/src/main/java/hudson/triggers/TimerTrigger.java
@@ -27,7 +27,6 @@
import static hudson.Util.fixNull;
-import antlr.ANTLRException;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.BuildableItem;
@@ -54,7 +53,7 @@
public class TimerTrigger extends Trigger {
@DataBoundConstructor
- public TimerTrigger(@NonNull String spec) throws ANTLRException {
+ public TimerTrigger(@NonNull String spec) {
super(spec);
}
@@ -95,10 +94,10 @@ public FormValidation doCheckSpec(@QueryParameter String value, @AncestorInPath
updateValidationsForSanity(validations, ctl);
updateValidationsForNextRun(validations, ctl);
return FormValidation.aggregate(validations);
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
if (value.trim().indexOf('\n') == -1 && value.contains("**"))
return FormValidation.error(Messages.TimerTrigger_MissingWhitespace());
- return FormValidation.error(e.getMessage());
+ return FormValidation.error(e, e.getMessage());
}
}
diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java
index b95003aabc47..f7415597d1a7 100644
--- a/core/src/main/java/hudson/triggers/Trigger.java
+++ b/core/src/main/java/hudson/triggers/Trigger.java
@@ -25,7 +25,6 @@
package hudson.triggers;
-import antlr.ANTLRException;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -100,7 +99,7 @@ public void start(J project, boolean newInstance) {
} else {
LOGGER.log(Level.WARNING, "The job {0} has a null crontab spec which is incorrect", job.getFullName());
}
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
// this shouldn't fail because we've already parsed stuff in the constructor,
// so if it fails, use whatever 'tabs' that we already have.
LOGGER.log(Level.WARNING, String.format("Failed to parse crontab spec %s in job %s", spec, project.getFullName()), e);
@@ -170,8 +169,11 @@ public TriggerDescriptor getDescriptor() {
* Creates a new {@link Trigger} that gets {@link #run() run}
* periodically. This is useful when your trigger does
* some polling work.
+ *
+ * @param cronTabSpec the crontab entry to be parsed
+ * @throws IllegalArgumentException if the crontab entry cannot be parsed
*/
- protected Trigger(@NonNull String cronTabSpec) throws ANTLRException {
+ protected Trigger(@NonNull String cronTabSpec) {
this.spec = cronTabSpec;
this.tabs = CronTabList.create(cronTabSpec);
}
@@ -196,7 +198,7 @@ public final String getSpec() {
protected Object readResolve() throws ObjectStreamException {
try {
tabs = CronTabList.create(spec);
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
InvalidObjectException x = new InvalidObjectException(e.getMessage());
x.initCause(e);
throw x;
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 4f92cc9fd408..eb9b8f58596a 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -42,7 +42,6 @@
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
-import antlr.ANTLRException;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -2071,7 +2070,7 @@ public Label getLabel(String expr) {
// For the record, this method creates temporary labels but there is a periodic task
// calling "trimLabels" to remove unused labels running every 5 minutes.
labels.putIfAbsent(expr, Label.parseExpression(expr));
- } catch (ANTLRException e) {
+ } catch (IllegalArgumentException e) {
// laxly accept it as a single label atom for backward compatibility
return getLabelAtom(expr);
}
diff --git a/core/src/main/java/jenkins/util/antlr/JenkinsANTLRErrorListener.java b/core/src/main/java/jenkins/util/antlr/JenkinsANTLRErrorListener.java
new file mode 100644
index 000000000000..0d150b10b698
--- /dev/null
+++ b/core/src/main/java/jenkins/util/antlr/JenkinsANTLRErrorListener.java
@@ -0,0 +1,53 @@
+package jenkins.util.antlr;
+
+import antlr.ANTLRException;
+import java.util.function.Supplier;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class JenkinsANTLRErrorListener extends BaseErrorListener {
+
+ private final Supplier errorMessageSupplier;
+
+ public JenkinsANTLRErrorListener() {
+ errorMessageSupplier = () -> null;
+ }
+
+ public JenkinsANTLRErrorListener(Supplier errorMessageSupplier) {
+ this.errorMessageSupplier = errorMessageSupplier;
+ }
+
+ @Override
+ public void syntaxError(
+ Recognizer, ?> recognizer,
+ Object offendingSymbol,
+ int line,
+ int charPositionInLine,
+ String msg,
+ RecognitionException e) {
+ String errorMessage = errorMessageSupplier.get();
+ if (errorMessage != null) {
+ msg = errorMessage;
+ }
+ throw new ANTLRException(formatMessage(line, charPositionInLine, msg), e);
+ }
+
+ private static String formatMessage(int line, int column, String message) {
+ StringBuilder sb = new StringBuilder();
+ if (line != -1) {
+ sb.append("line ");
+ sb.append(line);
+ if (column != -1) {
+ sb.append(":");
+ sb.append(column);
+ }
+ sb.append(": ");
+ }
+ sb.append(message);
+ return sb.toString();
+ }
+}
diff --git a/core/src/test/java/hudson/scheduler/CronTabEventualityTest.java b/core/src/test/java/hudson/scheduler/CronTabEventualityTest.java
index 56ae02a42ccd..73f1e55c5a77 100644
--- a/core/src/test/java/hudson/scheduler/CronTabEventualityTest.java
+++ b/core/src/test/java/hudson/scheduler/CronTabEventualityTest.java
@@ -2,7 +2,6 @@
import static org.junit.Assert.fail;
-import antlr.ANTLRException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
@@ -42,7 +41,7 @@ public CronTabEventualityTest(String name, Hash hash) {
@Test
@Issue("JENKINS-12388")
- public void testYearlyWillBeEventuallyTriggeredWithinOneYear() throws ANTLRException {
+ public void testYearlyWillBeEventuallyTriggeredWithinOneYear() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.YEAR, 1);
checkEventuality(start, "@yearly", limit);
@@ -50,56 +49,56 @@ public void testYearlyWillBeEventuallyTriggeredWithinOneYear() throws ANTLRExcep
@Test
@Issue("JENKINS-12388")
- public void testAnnuallyWillBeEventuallyTriggeredWithinOneYear() throws ANTLRException {
+ public void testAnnuallyWillBeEventuallyTriggeredWithinOneYear() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.YEAR, 1);
checkEventuality(start, "@annually", limit);
}
@Test
- public void testMonthlyWillBeEventuallyTriggeredWithinOneMonth() throws ANTLRException {
+ public void testMonthlyWillBeEventuallyTriggeredWithinOneMonth() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.MONTH, 1);
checkEventuality(start, "@monthly", limit);
}
@Test
- public void testWeeklyWillBeEventuallyTriggeredWithinOneWeek() throws ANTLRException {
+ public void testWeeklyWillBeEventuallyTriggeredWithinOneWeek() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.WEEK_OF_YEAR, 1);
checkEventuality(start, "@weekly", limit);
}
@Test
- public void testDailyWillBeEventuallyTriggeredWithinOneDay() throws ANTLRException {
+ public void testDailyWillBeEventuallyTriggeredWithinOneDay() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.DAY_OF_MONTH, 1);
checkEventuality(start, "@daily", limit);
}
@Test
- public void testMidnightWillBeEventuallyTriggeredWithinOneDay() throws ANTLRException {
+ public void testMidnightWillBeEventuallyTriggeredWithinOneDay() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.DAY_OF_MONTH, 1);
checkEventuality(start, "@midnight", limit);
}
@Test
- public void testHourlyWillBeEventuallyTriggeredWithinOneHour() throws ANTLRException {
+ public void testHourlyWillBeEventuallyTriggeredWithinOneHour() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.HOUR, 1);
checkEventuality(start, "@hourly", limit);
}
@Test
- public void testFirstDayOfMonthWillBeEventuallyTriggeredWithinOneMonth() throws ANTLRException {
+ public void testFirstDayOfMonthWillBeEventuallyTriggeredWithinOneMonth() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.MONTH, 1);
checkEventuality(start, "H H 1 * *", limit);
}
@Test
- public void testFirstSundayOfMonthWillBeEventuallyTriggeredWithinOneMonthAndOneWeek() throws ANTLRException {
+ public void testFirstSundayOfMonthWillBeEventuallyTriggeredWithinOneMonthAndOneWeek() {
Calendar start = new GregorianCalendar(2012, Calendar.JANUARY, 11, 22, 33); // Jan 11th 2012 22:33
Calendar limit = createLimit(start, Calendar.DAY_OF_MONTH, 31 + 7);
// If both day of month and day of week are specified:
@@ -108,7 +107,7 @@ public void testFirstSundayOfMonthWillBeEventuallyTriggeredWithinOneMonthAndOneW
checkEventuality(start, "H H 1-7 * 0", limit);
}
- private void checkEventuality(Calendar start, String crontabFormat, Calendar limit) throws ANTLRException {
+ private void checkEventuality(Calendar start, String crontabFormat, Calendar limit) {
CronTab cron = new CronTab(crontabFormat, hash);
Calendar next = cron.ceil(start);
if (next.after(limit)) {
diff --git a/core/src/test/java/hudson/scheduler/CronTabTest.java b/core/src/test/java/hudson/scheduler/CronTabTest.java
index d7f2c0f2e463..813ae6c375c5 100644
--- a/core/src/test/java/hudson/scheduler/CronTabTest.java
+++ b/core/src/test/java/hudson/scheduler/CronTabTest.java
@@ -25,6 +25,8 @@
package hudson.scheduler;
import static java.util.Calendar.MONDAY;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -46,7 +48,7 @@
public class CronTabTest {
@Test
- public void test1() throws ANTLRException {
+ public void test1() {
new CronTab("@yearly");
new CronTab("@weekly");
new CronTab("@midnight");
@@ -97,7 +99,7 @@ public void testCeil3_DoW7() throws Exception {
*/
@Issue("HUDSON-8656") // This is _not_ JENKINS-8656
@Test
- public void testCeil4() throws ANTLRException {
+ public void testCeil4() {
final Calendar cal = Calendar.getInstance(new Locale("de", "de"));
cal.set(2011, Calendar.JANUARY, 16, 0, 0, 0); // Sunday, Jan 16th 2011, 00:00
final String cronStr = "0 23 * * 1-5"; // execute on weekdays @23:00
@@ -119,7 +121,7 @@ public void testCeil4() throws ANTLRException {
*/
@Issue("HUDSON-8656") // This is _not_ JENKINS-8656
@Test
- public void testCeil5() throws ANTLRException {
+ public void testCeil5() {
final Calendar cal = Calendar.getInstance(new Locale("de", "at"));
cal.set(2011, Calendar.JANUARY, 16, 0, 0, 0); // Sunday, Jan 16th 2011, 00:00
final String cronStr = "0 23 * * 1-5"; // execute on weekdays @23:00
@@ -268,7 +270,8 @@ public int next(int n) {
ANTLRException e = assertThrows(ANTLRException.class, () ->
compare(new GregorianCalendar(2013, Calendar.MARCH, 21, 0, 0), new CronTab("H(0-3)/15 * * * *", Hash.from("junk")).ceil(new GregorianCalendar(2013, Calendar.MARCH, 21, 0, 0))));
- assertEquals("line 1:8: 15 is an invalid value. Must be within 1 and 4", e.toString());
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ assertEquals("line 1:9: 15 is an invalid value. Must be within 1 and 4", e.getMessage());
}
@Test public void repeatedHash() throws Exception {
@@ -288,12 +291,14 @@ public int next(int n) {
@Test public void rangeBoundsCheckFailHour() {
ANTLRException e = assertThrows(ANTLRException.class, () -> new CronTab("H H(12-24) * * *"));
- assertEquals("line 1:10: 24 is an invalid value. Must be within 0 and 23", e.toString());
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ assertEquals("line 1:10: 24 is an invalid value. Must be within 0 and 23", e.getMessage());
}
@Test public void rangeBoundsCheckFailMinute() {
ANTLRException e = assertThrows(ANTLRException.class, () -> new CronTab("H(33-66) * * * *"));
- assertEquals("line 1:8: 66 is an invalid value. Must be within 0 and 59", e.toString());
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ assertEquals("line 1:8: 66 is an invalid value. Must be within 0 and 59", e.getMessage());
}
@Issue("JENKINS-9283")
diff --git a/core/src/test/java/hudson/triggers/TimerTriggerTest.java b/core/src/test/java/hudson/triggers/TimerTriggerTest.java
index 88710db588f1..3d2a6ab01007 100644
--- a/core/src/test/java/hudson/triggers/TimerTriggerTest.java
+++ b/core/src/test/java/hudson/triggers/TimerTriggerTest.java
@@ -24,7 +24,6 @@
package hudson.triggers;
-import antlr.ANTLRException;
import hudson.scheduler.CronTabList;
import hudson.scheduler.Hash;
import java.util.TimeZone;
@@ -38,7 +37,7 @@
public class TimerTriggerTest {
@Issue("JENKINS-29790")
@Test
- public void testNoNPE() throws ANTLRException {
+ public void testNoNPE() {
new TimerTrigger("").run();
}
diff --git a/pom.xml b/pom.xml
index 9439db89b4e8..d90f8d83244e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,7 @@ THE SOFTWARE.
${maven.multiModuleProjectDirectory}/src/spotbugs/spotbugs-excludes.xml
1.27
+ 4.11.1
1.23
2.27.2
6.6
@@ -297,9 +298,9 @@ THE SOFTWARE.
${bridge-method-injector.version}
- org.codehaus.mojo
- antlr-maven-plugin
- 2.2
+ org.antlr
+ antlr4-maven-plugin
+ ${antlr.version}
org.apache.maven.plugins
@@ -383,6 +384,9 @@ THE SOFTWARE.
spotless-maven-plugin
${spotless.version}
+
+
+
diff --git a/src/spotbugs/spotbugs-excludes.xml b/src/spotbugs/spotbugs-excludes.xml
index d57cc5fb6aff..0971834a231e 100644
--- a/src/spotbugs/spotbugs-excludes.xml
+++ b/src/spotbugs/spotbugs-excludes.xml
@@ -22,11 +22,21 @@
+
+
+
+
+
+
+
+
+
+
@@ -38,6 +48,11 @@
+
+
+
+
+