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.jupiter.api.Test;
import org.apache.hadoop.test.AbstractHadoopTestBase;
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 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();
assertEquals("a-b", cat2.invoke(obj, "a", "b"),
"Should call the 2-arg version successfully");
assertEquals("a-b", cat2.invoke(obj, "a", "b", "c"),
"Should ignore extra arguments");
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();
assertEquals("a-b-c", cat3.invoke(obj, "a", "b", "c"),
"Should call the 3-arg version successfully");
assertEquals("a-b-null", cat3.invoke(obj, "a", "b"),
"Should call the 3-arg version null padding");
}
@Test
public void testVarArgs() throws Exception {
DynMethods.UnboundMethod cat = new DynMethods.Builder("concat")
.impl(Concatenator.class, String[].class)
.buildChecked();
assertEquals("abcde",
cat.invokeChecked(new Concatenator(), (Object) new String[]{"a", "b", "c", "d", "e"}),
"Should use the varargs version");
assertEquals("abcde",
cat.bind(new Concatenator())
.invokeChecked((Object) new String[]{"a", "b", "c", "d", "e"}),
"Should use the varargs version");
}
@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();
assertEquals("a-b", cat.invoke(obj, "a", "b"),
"Should find 2-arg concat method");
}
@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();
assertEquals("a-b", cat.invoke(obj, "a", "b"),
"Should find 2-arg concat method");
}
@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();
assertNotNull(changeSep, "Should find hidden method with hiddenImpl");
changeSep.invokeChecked(obj, "/");
assertEquals("a/b", obj.concat("a", "b"),
"Should use separator / instead of -");
}
@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("_"));
assertEquals("a-b", dashCat.invoke("a", "b"),
"Should use '-' object without passing");
assertEquals("a_b", underCat.invoke("a", "b"),
"Should use '_' object without passing");
DynMethods.BoundMethod slashCat = new DynMethods.Builder("concat")
.impl(Concatenator.class, String.class, String.class)
.buildChecked(new Concatenator("/"));
assertEquals("a/b", slashCat.invoke("a", "b"),
"Should use bound object from builder without passing");
}
@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();
assertTrue(staticCat.isStatic(), "Should be static");
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();
assertEquals("abcde", staticCat.invokeChecked(
(Object) new String[]{"a", "b", "c", "d", "e"}),
"Should call varargs static method cat(String...)");
}
@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();
assertFalse(cat2.isStatic(),
"concat(String,String) should not be static");
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();
assertTrue(newConcatenator instanceof DynConstructors.Ctor,
"Should find constructor implementation");
assertTrue(newConcatenator.isStatic(),
"Constructor should be a static method");
assertFalse(newConcatenator.isNoop(), "Constructor should not be NOOP");
// constructors cannot be bound
intercept(IllegalStateException.class, () ->
builder.buildChecked(new Concatenator()));
intercept(IllegalStateException.class, () ->
builder.build(new Concatenator()));
Concatenator concatenator = newConcatenator.asStatic().invoke("*");
assertEquals("a*b", concatenator.concat("a", "b"),
"Should function as a concatenator");
concatenator = newConcatenator.asStatic().invokeChecked("@");
assertEquals("a@b", concatenator.concat("a", "b"),
"Should function as a concatenator");
}
@Test
public void testConstructorImplAfterFactoryMethod() throws Exception {
DynMethods.UnboundMethod newConcatenator = new DynMethods.Builder("newConcatenator")
.impl(Concatenator.class, String.class)
.ctorImpl(Concatenator.class, String.class)
.buildChecked();
assertFalse(newConcatenator instanceof DynConstructors.Ctor,
"Should find factory method before constructor method");
}
@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();
assertTrue(noop.isNoop(), "No implementation found, should return NOOP");
assertNull(noop.invoke(new Concatenator(), "a"),
"NOOP should always return null");
assertNull(noop.invoke(null, "a"), "NOOP can be called with null");
assertNull(noop.bind(new Concatenator()).invoke("a"), "NOOP can be bound");
assertNull(noop.bind(null).invoke("a"), "NOOP can be bound to null");
assertNull(noop.asStatic().invoke("a"), "NOOP can be static");
}
}