TestDynMethods.java
/*
* 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.hadoop.util.dynamic;
import java.util.concurrent.Callable;
import org.junit.Assert;
import org.junit.Test;
import org.apache.hadoop.test.AbstractHadoopTestBase;
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
/**
* Copied from {@code org.apache.parquet.util} test suites.
*/
public class TestDynMethods extends AbstractHadoopTestBase {
@Test
public void testNoImplCall() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("concat");
intercept(NoSuchMethodException.class,
(Callable<DynMethods.UnboundMethod>) builder::buildChecked);
intercept(RuntimeException.class,
(Callable<DynMethods.UnboundMethod>) builder::build);
}
@Test
public void testMissingClass() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class);
intercept(NoSuchMethodException.class,
(Callable<DynMethods.UnboundMethod>) builder::buildChecked);
intercept(RuntimeException.class, () ->
builder.build());
}
@Test
public void testMissingMethod() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("concat")
.impl(Concatenator.class, "cat2strings", String.class, String.class);
intercept(NoSuchMethodException.class,
(Callable<DynMethods.UnboundMethod>) builder::buildChecked);
intercept(RuntimeException.class, () ->
builder.build());
}
@Test
public void testFirstImplReturned() throws Exception {
Concatenator obj = new Concatenator("-");
DynMethods.UnboundMethod cat2 = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class)
.impl(Concatenator.class, String.class, String.class)
.impl(Concatenator.class, String.class, String.class, String.class)
.buildChecked();
Assert.assertEquals("Should call the 2-arg version successfully",
"a-b", cat2.invoke(obj, "a", "b"));
Assert.assertEquals("Should ignore extra arguments",
"a-b", cat2.invoke(obj, "a", "b", "c"));
DynMethods.UnboundMethod cat3 = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class)
.impl(Concatenator.class, String.class, String.class, String.class)
.impl(Concatenator.class, String.class, String.class)
.build();
Assert.assertEquals("Should call the 3-arg version successfully",
"a-b-c", cat3.invoke(obj, "a", "b", "c"));
Assert.assertEquals("Should call the 3-arg version null padding",
"a-b-null", cat3.invoke(obj, "a", "b"));
}
@Test
public void testVarArgs() throws Exception {
DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl(Concatenator.class, String[].class)
.buildChecked();
Assert.assertEquals("Should use the varargs version", "abcde",
cat.invokeChecked(
new Concatenator(),
(Object) new String[]{"a", "b", "c", "d", "e"}));
Assert.assertEquals("Should use the varargs version", "abcde",
cat.bind(new Concatenator())
.invokeChecked((Object) new String[]{"a", "b", "c", "d", "e"}));
}
@Test
public void testIncorrectArguments() throws Exception {
final Concatenator obj = new Concatenator("-");
final DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class)
.impl(Concatenator.class, String.class, String.class)
.buildChecked();
intercept(IllegalArgumentException.class, () ->
cat.invoke(obj, 3, 4));
intercept(IllegalArgumentException.class, () ->
cat.invokeChecked(obj, 3, 4));
}
@Test
public void testExceptionThrown() throws Exception {
final Concatenator.SomeCheckedException exc = new Concatenator.SomeCheckedException();
final Concatenator obj = new Concatenator("-");
final DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class)
.impl(Concatenator.class, Exception.class)
.buildChecked();
intercept(Concatenator.SomeCheckedException.class, () ->
cat.invokeChecked(obj, exc));
intercept(RuntimeException.class, () ->
cat.invoke(obj, exc));
}
@Test
public void testNameChange() throws Exception {
Concatenator obj = new Concatenator("-");
DynMethods.UnboundMethod cat = new DynMethods.Builder("cat")
.impl(Concatenator.class, "concat", String.class, String.class)
.buildChecked();
Assert.assertEquals("Should find 2-arg concat method",
"a-b", cat.invoke(obj, "a", "b"));
}
@Test
public void testStringClassname() throws Exception {
Concatenator obj = new Concatenator("-");
DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl(Concatenator.class.getName(), String.class, String.class)
.buildChecked();
Assert.assertEquals("Should find 2-arg concat method",
"a-b", cat.invoke(obj, "a", "b"));
}
@Test
public void testHiddenMethod() throws Exception {
Concatenator obj = new Concatenator("-");
intercept(NoSuchMethodException.class, () ->
new DynMethods.Builder("setSeparator")
.impl(Concatenator.class, String.class)
.buildChecked());
DynMethods.UnboundMethod changeSep = new DynMethods.Builder("setSeparator")
.hiddenImpl(Concatenator.class, String.class)
.buildChecked();
Assert.assertNotNull("Should find hidden method with hiddenImpl",
changeSep);
changeSep.invokeChecked(obj, "/");
Assert.assertEquals("Should use separator / instead of -",
"a/b", obj.concat("a", "b"));
}
@Test
public void testBoundMethod() throws Exception {
DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl(Concatenator.class, String.class, String.class)
.buildChecked();
// Unbound methods can be bound multiple times
DynMethods.BoundMethod dashCat = cat.bind(new Concatenator("-"));
DynMethods.BoundMethod underCat = cat.bind(new Concatenator("_"));
Assert.assertEquals("Should use '-' object without passing",
"a-b", dashCat.invoke("a", "b"));
Assert.assertEquals("Should use '_' object without passing",
"a_b", underCat.invoke("a", "b"));
DynMethods.BoundMethod slashCat = new DynMethods.Builder("concat")
.impl(Concatenator.class, String.class, String.class)
.buildChecked(new Concatenator("/"));
Assert.assertEquals("Should use bound object from builder without passing",
"a/b", slashCat.invoke("a", "b"));
}
@Test
public void testBindStaticMethod() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("cat")
.impl(Concatenator.class, String[].class);
intercept(IllegalStateException.class, () ->
builder.buildChecked(new Concatenator()));
intercept(IllegalStateException.class, () ->
builder.build(new Concatenator()));
final DynMethods.UnboundMethod staticCat = builder.buildChecked();
Assert.assertTrue("Should be static", staticCat.isStatic());
intercept(IllegalStateException.class, () ->
staticCat.bind(new Concatenator()));
}
@Test
public void testStaticMethod() throws Exception {
DynMethods.StaticMethod staticCat = new DynMethods.Builder("cat")
.impl(Concatenator.class, String[].class)
.buildStaticChecked();
Assert.assertEquals("Should call varargs static method cat(String...)",
"abcde", staticCat.invokeChecked(
(Object) new String[]{"a", "b", "c", "d", "e"}));
}
@Test
public void testNonStaticMethod() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("concat")
.impl(Concatenator.class, String.class, String.class);
intercept(IllegalStateException.class, builder::buildStatic);
intercept(IllegalStateException.class, builder::buildStaticChecked);
final DynMethods.UnboundMethod cat2 = builder.buildChecked();
Assert.assertFalse("concat(String,String) should not be static",
cat2.isStatic());
intercept(IllegalStateException.class, cat2::asStatic);
}
@Test
public void testConstructorImpl() throws Exception {
final DynMethods.Builder builder = new DynMethods.Builder("newConcatenator")
.ctorImpl(Concatenator.class, String.class)
.impl(Concatenator.class, String.class);
DynMethods.UnboundMethod newConcatenator = builder.buildChecked();
Assert.assertTrue("Should find constructor implementation",
newConcatenator instanceof DynConstructors.Ctor);
Assert.assertTrue("Constructor should be a static method",
newConcatenator.isStatic());
Assert.assertFalse("Constructor should not be NOOP",
newConcatenator.isNoop());
// constructors cannot be bound
intercept(IllegalStateException.class, () ->
builder.buildChecked(new Concatenator()));
intercept(IllegalStateException.class, () ->
builder.build(new Concatenator()));
Concatenator concatenator = newConcatenator.asStatic().invoke("*");
Assert.assertEquals("Should function as a concatenator",
"a*b", concatenator.concat("a", "b"));
concatenator = newConcatenator.asStatic().invokeChecked("@");
Assert.assertEquals("Should function as a concatenator",
"a@b", concatenator.concat("a", "b"));
}
@Test
public void testConstructorImplAfterFactoryMethod() throws Exception {
DynMethods.UnboundMethod newConcatenator = new DynMethods.Builder("newConcatenator")
.impl(Concatenator.class, String.class)
.ctorImpl(Concatenator.class, String.class)
.buildChecked();
Assert.assertFalse("Should find factory method before constructor method",
newConcatenator instanceof DynConstructors.Ctor);
}
@Test
public void testNoop() throws Exception {
// noop can be unbound, bound, or static
DynMethods.UnboundMethod noop = new DynMethods.Builder("concat")
.impl("not.a.RealClass", String.class, String.class)
.orNoop()
.buildChecked();
Assert.assertTrue("No implementation found, should return NOOP",
noop.isNoop());
Assert.assertNull("NOOP should always return null",
noop.invoke(new Concatenator(), "a"));
Assert.assertNull("NOOP can be called with null",
noop.invoke(null, "a"));
Assert.assertNull("NOOP can be bound",
noop.bind(new Concatenator()).invoke("a"));
Assert.assertNull("NOOP can be bound to null",
noop.bind(null).invoke("a"));
Assert.assertNull("NOOP can be static",
noop.asStatic().invoke("a"));
}
}