This is an automated email from the ASF dual-hosted git repository.
gitgabrio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 9ecc8cef2d [incubator-kie-issues#507] DMN - Add a new "list replace"
function (#5645)
9ecc8cef2d is described below
commit 9ecc8cef2d3f2c38396c772aa8c9836546fae385
Author: Gabriele Cardosi <[email protected]>
AuthorDate: Mon Jan 15 15:24:29 2024 +0100
[incubator-kie-issues#507] DMN - Add a new "list replace" function (#5645)
* [incubator-kie-issues#507] Implement FEEL list replace
* [incubator-kie-issues#507] Fixed as per PR suggestion
* [incubator-kie-issues#507] Fix typo
---------
Co-authored-by: BAMOE CI <[email protected]>
---
.../feel/runtime/functions/BuiltInFunctions.java | 3 +-
.../runtime/functions/ListReplaceFunction.java | 86 +++++++++++++
.../org.kie/kie-dmn-feel/reflect-config.json | 9 ++
.../feel/codegen/feel11/DirectCompilerTest.java | 38 +-----
.../kie/dmn/feel/runtime/FEELFunctionsTest.java | 7 ++
.../runtime/functions/ListReplaceFunctionTest.java | 136 +++++++++++++++++++++
.../java/org/kie/dmn/feel/util/CompilerUtils.java | 73 +++++++++++
7 files changed, 316 insertions(+), 36 deletions(-)
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BuiltInFunctions.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BuiltInFunctions.java
index 9595d13cab..9495c98497 100644
---
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BuiltInFunctions.java
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BuiltInFunctions.java
@@ -118,7 +118,8 @@ public class BuiltInFunctions {
OverlapsBeforeFunction.INSTANCE,
OverlapsAfterFunction.INSTANCE,
MeetsFunction.INSTANCE,
- MetByFunction.INSTANCE
+ MetByFunction.INSTANCE,
+ ListReplaceFunction.INSTANCE
};
public static FEELFunction[] getFunctions() {
diff --git
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunction.java
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunction.java
new file mode 100644
index 0000000000..1ef80e1cbd
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunction.java
@@ -0,0 +1,86 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.kie.dmn.feel.runtime.functions;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
+import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
+import org.kie.dmn.feel.util.EvalHelper;
+
+public class ListReplaceFunction
+ extends BaseFEELFunction {
+
+ public static final ListReplaceFunction INSTANCE = new
ListReplaceFunction();
+
+ private static final String CANNOT_BE_NULL = "cannot be null";
+
+ private ListReplaceFunction() {
+ super("list replace");
+ }
+
+ public FEELFnResult<List> invoke(@ParameterName("list") List list,
@ParameterName("position") BigDecimal position,
+ @ParameterName("newItem") Object newItem)
{
+ if (list == null) {
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "list", CANNOT_BE_NULL));
+ }
+ if (position == null) {
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "position", CANNOT_BE_NULL));
+ }
+ int intPosition = position.intValue();
+ if (intPosition == 0 || Math.abs(intPosition) > list.size()) {
+ String paramProblem = String.format("%s outside valid boundaries
(1-%s)", intPosition, list.size());
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "position", paramProblem));
+ }
+ Object e = EvalHelper.coerceNumber(newItem);
+ List toReturn = new ArrayList(list);
+ int replacementPosition = intPosition > 0 ? intPosition -1 :
list.size() - Math.abs(intPosition);
+ toReturn.set(replacementPosition, e);
+ return FEELFnResult.ofResult(toReturn);
+ }
+
+ public FEELFnResult<List> invoke(@ParameterName("list") List list,
+ @ParameterName("match")
AbstractCustomFEELFunction match,
+ @ParameterName("newItem") Object newItem)
{
+ if (list == null) {
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "list", CANNOT_BE_NULL));
+ }
+ if (match == null) {
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "match", CANNOT_BE_NULL));
+ }
+ Object e = EvalHelper.coerceNumber(newItem);
+ List toReturn = new ArrayList();
+ for (Object o : list) {
+ Object matched =
match.invokeReflectively(match.getEvaluationContext(), new Object[]{o, e});
+ if (matched instanceof Boolean isMatch) {
+ if (isMatch) {
+ toReturn.add(e);
+ } else {
+ toReturn.add(o);
+ }
+ } else {
+ String paramProblem = String.format("%s returns '%s' instead
of boolean", match, matched);
+ return FEELFnResult.ofError(new
InvalidParametersEvent(Severity.ERROR, "match", paramProblem));
+ }
+ }
+ return FEELFnResult.ofResult(toReturn);
+ }
+}
diff --git
a/kie-dmn/kie-dmn-feel/src/main/resources/META-INF/native-image/org.kie/kie-dmn-feel/reflect-config.json
b/kie-dmn/kie-dmn-feel/src/main/resources/META-INF/native-image/org.kie/kie-dmn-feel/reflect-config.json
index fc3ff9ad2d..c6b830b456 100644
---
a/kie-dmn/kie-dmn-feel/src/main/resources/META-INF/native-image/org.kie/kie-dmn-feel/reflect-config.json
+++
b/kie-dmn/kie-dmn-feel/src/main/resources/META-INF/native-image/org.kie/kie-dmn-feel/reflect-config.json
@@ -296,6 +296,15 @@
"allDeclaredClasses": true,
"allPublicClasses": true
},
+ {
+ "name": "org.kie.dmn.feel.runtime.functions.ListReplaceFunction",
+ "allDeclaredConstructors": true,
+ "allPublicConstructors": true,
+ "allDeclaredMethods": true,
+ "allPublicMethods": true,
+ "allDeclaredClasses": true,
+ "allPublicClasses": true
+ },
{
"name": "org.kie.dmn.feel.runtime.functions.LogFunction",
"allDeclaredConstructors": true,
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java
index 32de44e2d9..edf20396a0 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/codegen/feel11/DirectCompilerTest.java
@@ -21,44 +21,30 @@ package org.kie.dmn.feel.codegen.feel11;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
-import java.util.Map;
-import com.github.javaparser.ast.expr.Expression;
-import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.Test;
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.FEELProperty;
import org.kie.dmn.feel.lang.Type;
-import org.kie.dmn.feel.lang.ast.BaseNode;
import org.kie.dmn.feel.lang.impl.JavaBackedType;
import org.kie.dmn.feel.lang.impl.MapBackedType;
import org.kie.dmn.feel.lang.types.BuiltInType;
-import org.kie.dmn.feel.parser.feel11.ASTBuilderVisitor;
-import org.kie.dmn.feel.parser.feel11.FEELParser;
import org.kie.dmn.feel.parser.feel11.FEELParserTest;
-import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser;
import org.kie.dmn.feel.runtime.FEELConditionsAndLoopsTest;
import org.kie.dmn.feel.runtime.FEELTernaryLogicTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.kie.dmn.feel.util.CompilerUtils.parse;
+import static org.kie.dmn.feel.util.CompilerUtils.parseCompileEvaluate;
import static org.kie.dmn.feel.util.DynamicTypeUtils.entry;
import static org.kie.dmn.feel.util.DynamicTypeUtils.mapOf;
public class DirectCompilerTest {
public static final Logger LOG =
LoggerFactory.getLogger(DirectCompilerTest.class);
-
- private Object parseCompileEvaluate(String feelLiteralExpression) {
- CompiledFEELExpression compiledExpression = parse(
feelLiteralExpression );
- LOG.debug("{}", compiledExpression);
-
- EvaluationContext emptyContext =
CodegenTestUtil.newEmptyEvaluationContext();
- Object result = compiledExpression.apply(emptyContext);
- LOG.debug("{}", result);
- return result;
- }
+
@Test
public void test_FEEL_number() {
@@ -470,24 +456,6 @@ public class DirectCompilerTest {
assertThat(result).isEqualTo(BigDecimal.valueOf(2016));
}
- private CompiledFEELExpression parse(String input) {
- return parse( input, Collections.emptyMap() );
- }
-
- private CompiledFEELExpression parse(String input, Map<String, Type>
inputTypes) {
- FEEL_1_1Parser parser = FEELParser.parse(null, input, inputTypes,
Collections.emptyMap(), Collections.emptyList(), Collections.emptyList(), null);
-
- ParseTree tree = parser.compilation_unit();
-
- ASTBuilderVisitor v = new ASTBuilderVisitor(inputTypes, null);
- BaseNode node = v.visit(tree);
- DirectCompilerResult directResult = node.accept(new
ASTCompilerVisitor());
-
- Expression expr = directResult.getExpression();
- CompiledFEELExpression cu = new
CompilerBytecodeLoader().makeFromJPExpression(input, expr,
directResult.getFieldDeclarations());
-
- return cu;
- }
}
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
index bb709bbdb4..5478e9a575 100644
---
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/FEELFunctionsTest.java
@@ -237,6 +237,13 @@ public class FEELFunctionsTest extends BaseFEELTest {
{ "{a: -2, r: -sum( 1, -abs(a), 3 )}.r", BigDecimal.valueOf(
-2 ) , null},
{ "if list contains ([2.2, 3.0, 4.0], 3) then \"OK\" else
\"NOT_OK\"", "OK" , null},
{ "if list contains ([2.2, 3, 4], 3.000) then \"OK\" else
\"NOT_OK\"", "OK" , null},
+ {"list replace ( null, 3, 6)", null ,
FEELEvent.Severity.ERROR},
+ {"list replace ( [2, 4, 7, 8], null, 6)", null ,
FEELEvent.Severity.ERROR},
+ {"list replace ( [2, 4, 7, 8], 3, 6)",
Arrays.asList(BigDecimal.valueOf(2), BigDecimal.valueOf(4),
BigDecimal.valueOf(6), BigDecimal.valueOf(8)), null},
+ {"list replace ( [2, 4, 7, 8], -3, 6)",
Arrays.asList(BigDecimal.valueOf(2), BigDecimal.valueOf(6),
BigDecimal.valueOf(7), BigDecimal.valueOf(8)), null},
+ {"list replace ( [2, 4, 7, 8], function(item, newItem) item +
newItem, 6)", null , FEELEvent.Severity.ERROR},
+ {"list replace ( [\"El-1\", \"El-2\", \"El-3\", \"El-4\"],
function(item, newItem) item = \"El-2\", null)", Arrays.asList("El-1", null,
"El-3", "El-4"), null},
+ {"list replace ( [2, 4, 7, 8], function(item, newItem) item <
newItem, 5)", Arrays.asList(BigDecimal.valueOf(5), BigDecimal.valueOf(5),
BigDecimal.valueOf(7), BigDecimal.valueOf(8)), null}
};
return addAdditionalParameters(cases, false);
}
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java
new file mode 100644
index 0000000000..ff85207cb0
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ListReplaceFunctionTest.java
@@ -0,0 +1,136 @@
+/**
+ * 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.kie.dmn.feel.runtime.functions;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.kie.dmn.feel.util.CompilerUtils.parseCompileEvaluate;
+
+public class ListReplaceFunctionTest {
+
+ private ListReplaceFunction listReplaceFunction;
+
+ @Before
+ public void setUp() {
+ listReplaceFunction = ListReplaceFunction.INSTANCE;
+ }
+
+ @Test
+ public void invokeListNull() {
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(null,
BigDecimal.ONE, ""), InvalidParametersEvent.class);
+ }
+
+ @Test
+ public void invokePositionNull() {
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(new
ArrayList(), (BigDecimal) null, ""), InvalidParametersEvent.class);
+ }
+
+ @Test
+ public void invokePositionInvalid() {
+
FunctionTestUtil.assertResultError(listReplaceFunction.invoke(Collections.emptyList(),
BigDecimal.ONE, ""), InvalidParametersEvent.class);
+ List list = getList();
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(list,
BigDecimal.ZERO, ""), InvalidParametersEvent.class);
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(list,
BigDecimal.valueOf(4), ""), InvalidParametersEvent.class);
+ }
+
+ @Test
+ public void invokeReplaceByPositionWithNull() {
+ List list = getList();
+ List expected = new ArrayList<>(list);
+ expected.set(1, null);
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
BigDecimal.valueOf(2), null), expected);
+ }
+
+ @Test
+ public void invokeReplaceByNegativePositionWithNotNull() {
+ List list = getList();
+ List expected = new ArrayList<>(list);
+ expected.set(2, "test");
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
BigDecimal.valueOf(-1), "test"), expected);
+ }
+
+ @Test
+ public void invokeReplaceByNegativePositionWithNull() {
+ List list = getList();
+ List expected = new ArrayList<>(list);
+ expected.set(2, null);
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
BigDecimal.valueOf(-1), null), expected);
+ }
+
+ @Test
+ public void invokeReplaceByPositionWithNotNull() {
+ List list = getList();
+ List expected = new ArrayList<>(list);
+ expected.set(1, "test");
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
BigDecimal.valueOf(2), "test"), expected);
+ }
+
+
+ @Test
+ public void invokeMatchNull() {
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(new
ArrayList(), (AbstractCustomFEELFunction) null, ""),
InvalidParametersEvent.class);
+ }
+
+ @Test
+ public void invokeMatchInvalid() {
+ List list = Arrays.asList(2, 4, 7, 8);
+ String validMatchFunction = "function(item, newItem) item + newItem";
+ Object expressionObject = parseCompileEvaluate(validMatchFunction);
+
assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class);
+ FunctionTestUtil.assertResultError(listReplaceFunction.invoke(list,
(AbstractCustomFEELFunction)expressionObject, 3), InvalidParametersEvent.class);
+ }
+
+ @Test
+ public void invokeReplaceByMatchWithNull() {
+ List list = getList();
+ List expected = new ArrayList<>(list);
+ expected.set(1, null);
+ String validMatchFunction = "function(item, newItem) item =
\"Element-1\"";
+ Object expressionObject = parseCompileEvaluate(validMatchFunction);
+
assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class);
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
(AbstractCustomFEELFunction)expressionObject, null), expected);
+ }
+
+ @Test
+ public void invokeReplaceByMatchWithNotNull() {
+ String validMatchFunction = "function(item, newItem) item < newItem";
+ Object expressionObject = parseCompileEvaluate(validMatchFunction);
+
assertThat(expressionObject).isInstanceOf(AbstractCustomFEELFunction.class);
+ List list = Arrays.asList(BigDecimal.valueOf(2),
BigDecimal.valueOf(4), BigDecimal.valueOf(7), BigDecimal.valueOf(8));
+ List expected = new ArrayList<>(list);
+ expected.set(0, BigDecimal.valueOf(5));
+ expected.set(1, BigDecimal.valueOf(5));
+ FunctionTestUtil.assertResult(listReplaceFunction.invoke(list,
(AbstractCustomFEELFunction)expressionObject, 5), expected);
+ }
+
+ private List getList() {
+ return IntStream.range(0, 3).mapToObj(i -> "Element-"+i).toList();
+ }
+
+}
\ No newline at end of file
diff --git
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java
new file mode 100644
index 0000000000..df1b63559d
--- /dev/null
+++
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CompilerUtils.java
@@ -0,0 +1,73 @@
+/**
+ * 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.kie.dmn.feel.util;
+
+import java.util.Collections;
+import java.util.Map;
+
+import com.github.javaparser.ast.expr.Expression;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.kie.dmn.feel.codegen.feel11.ASTCompilerVisitor;
+import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil;
+import org.kie.dmn.feel.codegen.feel11.CompiledFEELExpression;
+import org.kie.dmn.feel.codegen.feel11.CompilerBytecodeLoader;
+import org.kie.dmn.feel.codegen.feel11.DirectCompilerResult;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.Type;
+import org.kie.dmn.feel.lang.ast.BaseNode;
+import org.kie.dmn.feel.parser.feel11.ASTBuilderVisitor;
+import org.kie.dmn.feel.parser.feel11.FEELParser;
+import org.kie.dmn.feel.parser.feel11.FEEL_1_1Parser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CompilerUtils {
+
+ public static final Logger LOG =
LoggerFactory.getLogger(CompilerUtils.class);
+
+ public static Object parseCompileEvaluate(String feelLiteralExpression) {
+ CompiledFEELExpression compiledExpression = parse(
feelLiteralExpression );
+ LOG.debug("{}", compiledExpression);
+
+ EvaluationContext emptyContext =
CodegenTestUtil.newEmptyEvaluationContext();
+ Object result = compiledExpression.apply(emptyContext);
+ LOG.debug("{}", result);
+ return result;
+ }
+
+ public static CompiledFEELExpression parse(String input) {
+ return parse(input, Collections.emptyMap() );
+ }
+
+ public static CompiledFEELExpression parse(String input, Map<String, Type>
inputTypes) {
+ FEEL_1_1Parser parser = FEELParser.parse(null, input, inputTypes,
Collections.emptyMap(), Collections.emptyList(), Collections.emptyList(), null);
+
+ ParseTree tree = parser.compilation_unit();
+
+ ASTBuilderVisitor v = new ASTBuilderVisitor(inputTypes, null);
+ BaseNode node = v.visit(tree);
+ DirectCompilerResult directResult = node.accept(new
ASTCompilerVisitor());
+
+ Expression expr = directResult.getExpression();
+ CompiledFEELExpression cu = new
CompilerBytecodeLoader().makeFromJPExpression(input, expr,
directResult.getFieldDeclarations());
+
+ return cu;
+ }
+
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]