NIFI-6500 (Original commit messages left to preserve authorship credit for multiple contributors)

Expression Language now supports padding functions:
- padLeft:
  -  attr:padLeft(Int n, Char c) will prepend to the `attr` attributes the `c` character until the size `n` is reached
  -  attr:padLeft(Int n) will prepend to the `attr` attributes the `'_'` character until the size `n` is reached
- padRight:
  -  attr:padRight(Int n, Char c) will append to the `attr` attributes the `c` character until the size `n` is reached
  -  attr:padRight(Int n) will append to the `attr` attributes the `'_'` character until the size `n` is reached

- In both cases, the padding function returns the `attr` `String` as is if its length is already equal of higher than the desired size `n`
- Returns null if `attr` does not exist or in case desiredLenght is higher than Integer.MAX_INT

Further test cases:
- Returns null if the input string is null
- Returns a string full of padding if the input string is empty
Supports PaddingString instead of PaddingCharacter
Apply suggestions from code review
Applying style suggestions
Co-Authored-By: Marco Gaido <marcogaido91@gmail.com>
style fixes
style fixes
Padding returns input string instead of null in case desired length is missing, is negative, or is overflowing
Better tests
doc update
less verbose parser notation
Doc and style fixes
Fixed `StringEvaluator.evaluate()` after rebase
Applying nitpicking suggestion
Co-Authored-By: Marco Gaido <marcogaido91@gmail.com>
Fixed `PaddingEvaluator` constructor issue
Removed unused import

This closes #3615

Signed-off-by: Mike Thomsen <mthomsen@apache.org>
This commit is contained in:
Alessandro D'Armiento 2019-07-30 15:05:32 +02:00 committed by Mike Thomsen
parent 0b74795578
commit bb9758be2c
No known key found for this signature in database
GPG Key ID: 88511C3D4CAD246F
8 changed files with 282 additions and 1 deletions

View File

@ -188,6 +188,8 @@ REPLACE : 'replace';
REPLACE_FIRST : 'replaceFirst'; REPLACE_FIRST : 'replaceFirst';
REPLACE_ALL : 'replaceAll'; REPLACE_ALL : 'replaceAll';
IF_ELSE : 'ifElse'; IF_ELSE : 'ifElse';
PAD_LEFT : 'padLeft';
PAD_RIGHT : 'padRight';
// 4 arg functions // 4 arg functions
GET_DELIMITED_FIELD : 'getDelimitedField'; GET_DELIMITED_FIELD : 'getDelimitedField';

View File

@ -79,7 +79,7 @@ oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST | SUBSTRING_AFTER | SU
PREPEND | APPEND | STARTS_WITH | ENDS_WITH | CONTAINS | JOIN | JSON_PATH | JSON_PATH_DELETE | FROM_RADIX) LPAREN! anyArg RPAREN!) | PREPEND | APPEND | STARTS_WITH | ENDS_WITH | CONTAINS | JOIN | JSON_PATH | JSON_PATH_DELETE | FROM_RADIX) LPAREN! anyArg RPAREN!) |
(TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!); (TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE) LPAREN! anyArg COMMA! anyArg RPAREN!) | twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE) LPAREN! anyArg COMMA! anyArg RPAREN!) |
((SUBSTRING | FORMAT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!); ((SUBSTRING | FORMAT | PAD_LEFT | PAD_RIGHT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!; fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!;
// functions that return Booleans // functions that return Booleans

View File

@ -78,6 +78,8 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.NowEva
import org.apache.nifi.attribute.expression.language.evaluation.functions.NumberToDateEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.NumberToDateEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.OneUpSequenceEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.OneUpSequenceEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.OrEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.OrEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.PadLeftEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.PadRightEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.PlusEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.PlusEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.PrependEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.PrependEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.RandomNumberGeneratorEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.RandomNumberGeneratorEvaluator;
@ -190,6 +192,8 @@ import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT_NULL; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT_NULL;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOW; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOW;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.OR; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.OR;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PAD_LEFT;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PAD_RIGHT;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PARAMETER_REFERENCE; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PARAMETER_REFERENCE;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PLUS; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PLUS;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PREPEND; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PREPEND;
@ -657,6 +661,28 @@ public class ExpressionCompiler {
toStringEvaluator(argEvaluators.get(0), "first argument to replaceAll"), toStringEvaluator(argEvaluators.get(0), "first argument to replaceAll"),
toStringEvaluator(argEvaluators.get(1), "second argument to replaceAll")), "replaceAll"); toStringEvaluator(argEvaluators.get(1), "second argument to replaceAll")), "replaceAll");
} }
case PAD_LEFT: {
if (argEvaluators.size() == 1) {
return addToken(new PadLeftEvaluator(toStringEvaluator(subjectEvaluator),
toWholeNumberEvaluator(argEvaluators.get(0), "desired string length")),
"padLeft");
} else {
return addToken(new PadLeftEvaluator(toStringEvaluator(subjectEvaluator),
toWholeNumberEvaluator(argEvaluators.get(0), "desired string length"),
toStringEvaluator(argEvaluators.get(1), "padding string")), "padLeft");
}
}
case PAD_RIGHT: {
if (argEvaluators.size() == 1) {
return addToken(new PadRightEvaluator(toStringEvaluator(subjectEvaluator),
toWholeNumberEvaluator(argEvaluators.get(0), "desired string length")),
"padRight");
} else {
return addToken(new PadRightEvaluator(toStringEvaluator(subjectEvaluator),
toWholeNumberEvaluator(argEvaluators.get(0), "desired string length"),
toStringEvaluator(argEvaluators.get(1), "padding string")), "padRight");
}
}
case APPEND: { case APPEND: {
verifyArgCount(argEvaluators, 1, "append"); verifyArgCount(argEvaluators, 1, "append");
return addToken(new AppendEvaluator(toStringEvaluator(subjectEvaluator), return addToken(new AppendEvaluator(toStringEvaluator(subjectEvaluator),

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.attribute.expression.language.evaluation.functions;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
public class PadLeftEvaluator extends PaddingEvaluator {
public PadLeftEvaluator(Evaluator<String> subject, Evaluator<Long> desiredLength, Evaluator<String> pad) {
super(subject, desiredLength, pad);
}
public PadLeftEvaluator(Evaluator<String> subject, Evaluator<Long> desiredLength) {
super(subject, desiredLength, null);
}
@Override
protected String doPad(String subjectValue, int desiredLengthValue, String padValue) {
return StringUtils.leftPad(subjectValue, desiredLengthValue, padValue);
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.attribute.expression.language.evaluation.functions;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
public class PadRightEvaluator extends PaddingEvaluator {
public PadRightEvaluator(Evaluator<String> subject, Evaluator<Long> desiredLength, Evaluator<String> pad) {
super(subject, desiredLength, pad);
}
public PadRightEvaluator(Evaluator<String> subject, Evaluator<Long> desiredLength) {
super(subject, desiredLength, null);
}
@Override
protected String doPad(String subjectValue, int desiredLengthValue, String padValue) {
return StringUtils.rightPad(subjectValue, desiredLengthValue, padValue);
}
}

View File

@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.attribute.expression.language.evaluation.functions;
import org.apache.nifi.attribute.expression.language.EvaluationContext;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator;
abstract class PaddingEvaluator extends StringEvaluator {
public static final String DEFAULT_PADDING_STRING = "_";
private final Evaluator<String> subject;
private final Evaluator<Long> desiredLength;
private final Evaluator<String> pad;
PaddingEvaluator(final Evaluator<String> subject, final Evaluator<Long> desiredLength, final Evaluator<String> pad) {
this.subject = subject;
this.desiredLength = desiredLength;
this.pad = pad;
}
@Override
public QueryResult<String> evaluate(EvaluationContext evaluationContext) {
final String subjectValue = subject.evaluate(evaluationContext).getValue();
if (subjectValue == null) {
return new StringQueryResult(null);
}
final Long desiredLengthValue = desiredLength.evaluate(evaluationContext).getValue();
if (desiredLengthValue == null || desiredLengthValue > Integer.MAX_VALUE || desiredLengthValue <= 0) {
return new StringQueryResult(subjectValue);
}
String padValue = DEFAULT_PADDING_STRING;
if (pad != null) {
String s = pad.evaluate(evaluationContext).getValue();
if (s != null && !s.isEmpty()) {
padValue = s;
}
}
return new StringQueryResult(doPad(subjectValue, desiredLengthValue.intValue(), padValue));
}
protected abstract String doPad(String subjectValue, int desiredLengthValue, String padValue);
@Override
public Evaluator<?> getSubjectEvaluator() {
return subject;
}
}

View File

@ -74,6 +74,10 @@ public class TestQuery {
assertValid("${literal(3)}"); assertValid("${literal(3)}");
assertValid("${random()}"); assertValid("${random()}");
assertValid("${getStateValue('the_count')}"); assertValid("${getStateValue('the_count')}");
assertValid("${attr:padLeft(10, '#')}");
assertValid("${attr:padRight(10, '#')}");
assertValid("${attr:padLeft(10)}");
assertValid("${attr:padRight(10)}");
// left here because it's convenient for looking at the output // left here because it's convenient for looking at the output
//System.out.println(Query.compile("").evaluate(null)); //System.out.println(Query.compile("").evaluate(null));
} }
@ -1902,6 +1906,44 @@ public class TestQuery {
verifyEquals("${thread()}", attributes, "main"); verifyEquals("${thread()}", attributes, "main");
} }
@Test
public void testPadLeft() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
attributes.put("emptyString", "");
attributes.put("nullString", null);
verifyEquals("${attr:padLeft(10, '@')}", attributes, "@@@@@hello");
verifyEquals("${attr:padLeft(10)}", attributes, "_____hello");
verifyEquals("${attr:padLeft(10, \"xy\")}", attributes, "xyxyxhello");
verifyEquals("${attr:padLeft(10, \"aVeryLongPaddingString\")}", attributes, "aVeryhello");
verifyEquals("${attr:padLeft(1, \"a\")}", attributes, "hello");
verifyEquals("${attr:padLeft(-10, \"a\")}", attributes, "hello");
verifyEquals("${emptyString:padLeft(10, '@')}", attributes, "@@@@@@@@@@");
verifyEquals("${attr:padLeft(9999999999, \"abc\")}", attributes, "hello");
verifyEmpty("${nonExistingAttr:padLeft(10, \"abc\")}", attributes);
verifyEmpty("${nullString:padLeft(10, \"@\")}", attributes);
}
@Test
public void testPadRight() {
final Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "hello");
attributes.put("emptyString", "");
attributes.put("nullString", null);
verifyEquals("${attr:padRight(10, '@')}", attributes, "hello@@@@@");
verifyEquals("${attr:padRight(10)}", attributes, "hello_____");
verifyEquals("${attr:padRight(10, \"xy\")}", attributes, "helloxyxyx");
verifyEquals("${attr:padRight(10, \"aVeryLongPaddingString\")}", attributes, "helloaVery");
verifyEquals("${attr:padRight(1, \"a\")}", attributes, "hello");
verifyEquals("${attr:padRight(-10, \"a\")}", attributes, "hello");
verifyEquals("${emptyString:padRight(10, '@')}", attributes, "@@@@@@@@@@");
verifyEquals("${attr:padRight(9999999999, \"abc\")}", attributes, "hello");
verifyEmpty("${nonExistingAttr:padRight(10, \"abc\")}", attributes);
verifyEmpty("${nullString:padRight(10, \"@\")}", attributes);
}
private void verifyEquals(final String expression, final Map<String, String> attributes, final Object expectedResult) { private void verifyEquals(final String expression, final Map<String, String> attributes, final Object expectedResult) {
verifyEquals(expression,attributes, null, ParameterLookup.EMPTY, expectedResult); verifyEquals(expression,attributes, null, ParameterLookup.EMPTY, expectedResult);
} }

View File

@ -977,6 +977,76 @@ Expressions will provide the following results:
[.function]
=== padLeft
*Description*: [.description]#The `padLeft` function prepends the given padding string (or `'_'`, if nothing is provided) to the argument `String` until the passed desired length is reached.
It returns the argument as is if its length is already equal or higher than the desired length, if the padding string is `null`, and if the desired length is either negative or greater than `Integer.MAX_VALUE`.
It returns `null` if the argument string is not a valid attribute.
*Subject Type*: [.subject]#String#
*Arguments*:
- [.argName]#_DesiredLength_# : [.argDesc]#The integer value to pad to.#
- [.argName]#_PaddingString_# : [.argDesc]#The optional string to pad with. `"_"` will be used if a `PaddingString` is not provided. If the `PaddingString` is not an exact multiple of the actual pad size, it will be trimmed to fit in `DesiredLength`.#
*Return Type*: [.returnType]#String#
*Examples*: If the "greetings" attribute has the value "hello", then the following
Expressions will provide the following results:
.PadLeft Examples
|=======================================================================================
| Expression | Value
| `${greetings:padLeft(10)}` | `\_____hello`
| `${greetings:padLeft(10, '@')}` | `@@@@@hello`
| `${greetings:padLeft(10, 'xy')}` | `xyxyxhello`
| `${greetings:padLeft(10, 'aVeryLongPaddingString')}` | `aVeryhello`
|=======================================================================================
[.function]
=== padRight
*Description*: [.description]#The `padRight` function appends the given padding string (or `'_'`, if nothing is provided) to the argument `String` until the passed desired length is reached.
It returns the argument as is if its length is already equal or higher than the desired length, if the padding string is `null`, and if the desired length is either negative or greater than `Integer.MAX_VALUE`.
It returns `null` if the argument string is not a valid attribute.
*Subject Type*: [.subject]#String#
*Arguments*:
- [.argName]#_DesiredLength_# : [.argDesc]#The integer value to pad to.#
- [.argName]#_PaddingString_# : [.argDesc]#The optional string to pad with. `"_"` will be used if a `PaddingString` is not provided. If the `PaddingString` is not an exact multiple of the actual pad size, it will be trimmed to fit in `DesiredLength`.#
*Return Type*: [.returnType]#String#
*Examples*: If the "greetings" attribute has the value "hello", then the following
Expressions will provide the following results:
.PadLeft Examples
|=======================================================================================
| Expression | Value
| `${greetings:padRight(10)}` | `hello\_____`
| `${greetings:padRight(10, '@')}` | `hello@@@@@`
| `${greetings:padRight(10, 'xy')}` | `helloxyxyx`
| `${greetings:padLeft(10, 'aVeryLongPaddingString')}` | `helloaVery`
|=======================================================================================
[.function] [.function]
=== replaceNull === replaceNull