Skip to content

Commit

Permalink
[#1170]: Implement fn:replace
Browse files Browse the repository at this point in the history
This change implements a function of 'fn:replace', which maps to the
Java "String::replace" method.

Signed-off-by: Jens Reimann <jreimann@redhat.com>
  • Loading branch information
ctron committed Sep 13, 2021
1 parent 460f171 commit 073a935
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,4 @@ The following functions are provided by Ditto out of the box:
| `fn:substring-after` | `(String givenString)` | Parses the result of the previous expression and passes along only the characters _after_ the first occurrence of `givenString`.<br/>If `givenString` is not contained, this function will resolve to `empty`. | `fn:substring-after(':')`<br/>`fn:substring-after(":")` |
| `fn:lower` | `()` | Makes the String result of the previous expression lowercase in total. | `fn:lower()` |
| `fn:upper` | `()` | Makes the String result of the previous expression uppercase in total. | `fn:upper()` |
| `fn:replace` | `(String from, String to)` | Replaces a string with another using Java's `String::replace` method. | `fn:replace('foo', 'bar')` |
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ final class ImmutableFunctionExpression implements FunctionExpression {
new PipelineFunctionSubstringAfter(), // fn:substring-after(':')
new PipelineFunctionLower(), // fn:lower()
new PipelineFunctionUpper(), // fn:upper()
new PipelineFunctionDelete() // fn:delete()
new PipelineFunctionDelete(), // fn:delete()
new PipelineFunctionReplace() // fn:replace('from', 'to')
));

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.internal.models.placeholders;

import javax.annotation.concurrent.Immutable;
import java.util.Arrays;
import java.util.List;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

/**
* Provides the {@code fn:replace('from', 'to')} function implementation.
*/
@Immutable
final class PipelineFunctionReplace implements PipelineFunction {

private static final String FUNCTION_NAME = "replace";

@Override
public String getName() {
return FUNCTION_NAME;
}

@Override
public Signature getSignature() {
return ReplaceFunctionSignature.INSTANCE;
}

@Override
public PipelineElement apply(final PipelineElement value, final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

final Parameters parameters = parseAndResolve(paramsIncludingParentheses, expressionResolver);

return value.map(str -> str.replace(parameters.getFrom(), parameters.getTo()));
}

private PipelineFunctionReplace.Parameters parseAndResolve(final String paramsIncludingParentheses,
final ExpressionResolver expressionResolver) {

final List<PipelineElement> parameterElements =
PipelineFunctionParameterResolverFactory.forDoubleOrTripleStringOrPlaceholderParameter()
.apply(paramsIncludingParentheses, expressionResolver, this);

final PipelineFunctionReplace.ParametersBuilder parametersBuilder = new PipelineFunctionReplace.ParametersBuilder();

final PipelineElement fromParamElement = parameterElements.get(0);
final String fromParam = fromParamElement.toOptional().orElse("");
parametersBuilder.withFrom(fromParam);

final PipelineElement toParamElement = parameterElements.get(1);
final String toParam = toParamElement.toOptional().orElseThrow(() ->
PlaceholderFunctionSignatureInvalidException.newBuilder(paramsIncludingParentheses, this)
.build());
parametersBuilder.withTo(toParam);

return parametersBuilder.build();
}

/**
* Describes the signature of the {@code replace('from', 'to')} function.
*/
private static final class ReplaceFunctionSignature implements Signature {

private static final ReplaceFunctionSignature INSTANCE = new ReplaceFunctionSignature();

private final ParameterDefinition<String> fromStringDescription;
private final ParameterDefinition<String> toStringDescription;

private ReplaceFunctionSignature() {
fromStringDescription = new FromStringParam();
toStringDescription = new ToStringParam();
}

@Override
public List<ParameterDefinition<?>> getParameterDefinitions() {
return Arrays.asList(fromStringDescription, toStringDescription);
}

@Override
public String toString() {
return renderSignature();
}

}

/**
* Describes the from param of the {@code replace('from', 'to')} function.
*/
private static final class FromStringParam implements ParameterDefinition<String> {

private FromStringParam() {
}

@Override
public String getName() {
return "from";
}

@Override
public Class<String> getType() {
return String.class;
}

@Override
public String getDescription() {
return "Specifies the string to search for and replace";
}

}

/**
* Describes the to param of the {@code replace('from', 'to')} function.
*/
private static final class ToStringParam implements ParameterDefinition<String> {

private ToStringParam() {
}

@Override
public String getName() {
return "to";
}

@Override
public Class<String> getType() {
return String.class;
}

@Override
public String getDescription() {
return "Specifies the replace string to be inserted for all matching 'from' strings";
}

}


@Immutable
private static final class Parameters {
private final String from;
private final String to;

private Parameters(
final String from,
final String to) {
this.from = checkNotNull(from, "from");
this.to = checkNotNull(to, "to");
}

private String getFrom() {
return from;
}

private String getTo() {
return to;
}

}

private static final class ParametersBuilder {
private String from;
private String to;

PipelineFunctionReplace.ParametersBuilder withFrom(final String from) {
this.from = from;
return this;
}

PipelineFunctionReplace.ParametersBuilder withTo(final String to) {
this.to = to;
return this;
}

PipelineFunctionReplace.Parameters build() {
return new PipelineFunctionReplace.Parameters(from, to);
}

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class ImmutableFunctionExpressionTest {
"substring-after",
"lower",
"upper",
"delete"
"delete",
"replace"
)));
private static final HeadersPlaceholder HEADERS_PLACEHOLDER = PlaceholderFactory.newHeadersPlaceholder();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.internal.models.placeholders;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.verifyZeroInteractions;

@RunWith(MockitoJUnitRunner.class)
public class PipelineFunctionReplaceTest {

private static final PipelineElement EMPTY_INPUT = PipelineElement.unresolved();

private static final PipelineElement KNOWN_INPUT = PipelineElement.resolved("eclipse-iot-test");
private static final String EXPECTED_OUTPUT = "eclipse_iot_test";

private static final PipelineElement KNOWN_INPUT_NO_MATCH = PipelineElement.resolved("eclipse");
private static final String EXPECTED_OUTPUT_NO_MATCH = "eclipse";

private final PipelineFunctionReplace function = new PipelineFunctionReplace();

@Mock
private ExpressionResolver expressionResolver;

@After
public void verifyExpressionResolverUnused() {
verifyZeroInteractions(expressionResolver);
}

@Test
public void getName() {
assertThat(function.getName()).isEqualTo("replace");
}

@Test
public void apply() {
assertThat(function.apply(KNOWN_INPUT, "('-', '_')", expressionResolver)).contains(EXPECTED_OUTPUT);
}

@Test
public void applyNoMatch() {
assertThat(function.apply(KNOWN_INPUT_NO_MATCH, "('-', '_')", expressionResolver)).contains(EXPECTED_OUTPUT_NO_MATCH);
}

@Test
public void returnsEmptyForEmptyInput() {
assertThat(function.apply(EMPTY_INPUT, "('-', '_')", expressionResolver)).isEmpty();
}

@Test
public void throwsOnInvalidParameters() {
// has not enough parameters
assertThatExceptionOfType(PlaceholderFunctionSignatureInvalidException.class).isThrownBy(() ->
function.apply(KNOWN_INPUT, "()", expressionResolver)
);
// has not enough parameters
assertThatExceptionOfType(PlaceholderFunctionSignatureInvalidException.class).isThrownBy(() ->
function.apply(KNOWN_INPUT, "('from')", expressionResolver)
);
// has no parameters
assertThatExceptionOfType(PlaceholderFunctionSignatureInvalidException.class).isThrownBy(() ->
function.apply(KNOWN_INPUT, "", expressionResolver)
);
}

}

0 comments on commit 073a935

Please sign in to comment.