diff --git a/main/src/com/google/refine/expr/functions/Coalesce.java b/main/src/com/google/refine/expr/functions/Coalesce.java new file mode 100644 index 000000000..5ef489c35 --- /dev/null +++ b/main/src/com/google/refine/expr/functions/Coalesce.java @@ -0,0 +1,72 @@ +/* + +Copyright 2018, Owen Stephens +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package com.google.refine.expr.functions; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONWriter; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; + +public class Coalesce implements Function { + + @Override + public Object call(Properties bindings, Object[] args) { + if (args.length> 1) { + for (int i = 0; i < args.length; i++){ + if (args[i] == null) { + continue; + } else { + return args[i]; + } + } + return null; + } + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects at least two arguments"); + } + + @Override + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("description"); writer.value("Returns the first non-null from a series of values"); + writer.key("params"); writer.value("two or more objects"); + writer.key("returns"); writer.value("object or null"); + writer.endObject(); + } +} diff --git a/main/src/com/google/refine/grel/ControlFunctionRegistry.java b/main/src/com/google/refine/grel/ControlFunctionRegistry.java index abc343a74..083f5e697 100644 --- a/main/src/com/google/refine/grel/ControlFunctionRegistry.java +++ b/main/src/com/google/refine/grel/ControlFunctionRegistry.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import com.google.refine.expr.functions.Coalesce; import com.google.refine.expr.functions.Cross; import com.google.refine.expr.functions.FacetCount; import com.google.refine.expr.functions.Get; @@ -186,6 +187,7 @@ public class ControlFunctionRegistry { } static { + registerFunction("coalesce", new Coalesce()); registerFunction("type", new Type()); registerFunction("toString", new ToString()); diff --git a/main/tests/server/src/com/google/refine/tests/expr/functions/CoalesceTests.java b/main/tests/server/src/com/google/refine/tests/expr/functions/CoalesceTests.java new file mode 100644 index 000000000..a2f1be5fa --- /dev/null +++ b/main/tests/server/src/com/google/refine/tests/expr/functions/CoalesceTests.java @@ -0,0 +1,103 @@ +/* + +Copyright 2011, Owen Stephens +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +package com.google.refine.tests.expr.functions.strings; + +import java.util.Properties; + +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.google.refine.expr.EvalError; +import com.google.refine.grel.ControlFunctionRegistry; +import com.google.refine.grel.Function; +import com.google.refine.tests.RefineTest; + + +public class CoalesceTests extends RefineTest { + + private static Properties bindings; + private static final Integer[] ZERO_TO_TWO = new Integer[] {0, 1, 2}; + + @Override + @BeforeTest + public void init() { + logger = LoggerFactory.getLogger(this.getClass()); + } + + @BeforeMethod + public void setUp() { + bindings = new Properties(); + } + + @AfterMethod + public void tearDown() { + bindings = null; + } + + /** + * Lookup a control function by name and invoke it with a variable number of args + */ + private static Object invoke(String name,Object... args) { + // registry uses static initializer, so no need to set it up + Function function = ControlFunctionRegistry.getFunction(name); + if (function == null) { + throw new IllegalArgumentException("Unknown function "+name); + } + if (args == null) { + return function.call(bindings,new Object[0]); + } else { + return function.call(bindings,args); + } + } + + @Test + public void testCoalesceInvalidParams() { + Assert.assertTrue(invoke("coalesce") instanceof EvalError); + Assert.assertTrue(invoke("coalesce", 1) instanceof EvalError); + } + + @Test + public void testCoalesce() { + Assert.assertNull(invoke("coalesce", (Object) null, (Object) null)); + Assert.assertEquals(invoke("coalesce", (Object) null, "string"),"string"); + Assert.assertEquals(invoke("coalesce", (Object) null, (Object) null, "string"),"string"); + Assert.assertEquals(invoke("coalesce", (Object) null, 1),1); + Assert.assertEquals(invoke("coalesce", (Object) null, ZERO_TO_TWO),ZERO_TO_TWO); + } + +}