Repository: parquet-mr Updated Branches: refs/heads/master df9f8d869 -> ddbeb4dd1
http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java ---------------------------------------------------------------------- diff --git a/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java b/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java new file mode 100644 index 0000000..769f31c --- /dev/null +++ b/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java @@ -0,0 +1,520 @@ +/* + * 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.parquet.util; + +import org.apache.parquet.Preconditions; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; + +import static org.apache.parquet.Exceptions.throwIfInstance; + +public class DynMethods { + + /** + * Convenience wrapper class around {@link java.lang.reflect.Method}. + * + * Allows callers to invoke the wrapped method with all Exceptions wrapped by + * RuntimeException, or with a single Exception catch block. + */ + public static class UnboundMethod { + + private final Method method; + private final String name; + private final int argLength; + + UnboundMethod(Method method, String name) { + this.method = method; + this.name = name; + this.argLength = (method == null || method.isVarArgs()) ? -1 : + method.getParameterTypes().length; + } + + @SuppressWarnings("unchecked") + public <R> R invokeChecked(Object target, Object... args) throws Exception { + try { + if (argLength < 0) { + return (R) method.invoke(target, args); + } else { + return (R) method.invoke(target, Arrays.copyOfRange(args, 0, argLength)); + } + + } catch (InvocationTargetException e) { + throwIfInstance(e.getCause(), Exception.class); + throwIfInstance(e.getCause(), RuntimeException.class); + throw new RuntimeException(e.getCause()); + } + } + + public <R> R invoke(Object target, Object... args) { + try { + return this.<R>invokeChecked(target, args); + } catch (Exception e) { + throwIfInstance(e, RuntimeException.class); + throw new RuntimeException(e); + } + } + + /** + * Returns this method as a BoundMethod for the given receiver. + * + * @param receiver an Object to receive the method invocation + * @return a {@link BoundMethod} for this method and the receiver + * @throws IllegalStateException if the method is static + * @throws IllegalArgumentException if the receiver's class is incompatible + */ + public BoundMethod bind(Object receiver) { + Preconditions.checkState(!isStatic(), + "Cannot bind static method " + method.toGenericString()); + Preconditions.checkArgument( + method.getDeclaringClass().isAssignableFrom(receiver.getClass()), + "Cannot bind " + method.toGenericString() + " to instance of " + + receiver.getClass()); + + return new BoundMethod(this, receiver); + } + + /** + * @return whether the method is a static method + */ + public boolean isStatic() { + return Modifier.isStatic(method.getModifiers()); + } + + /** + * @return whether the method is a noop + */ + public boolean isNoop() { + return this == NOOP; + } + + /** + * Returns this method as a StaticMethod. + * + * @return a {@link StaticMethod} for this method + * @throws IllegalStateException if the method is not static + */ + public StaticMethod asStatic() { + Preconditions.checkState(isStatic(), "Method is not static"); + return new StaticMethod(this); + } + + public String toString() { + return "DynMethods.UnboundMethod(name=" + name +" method=" + + method.toGenericString() + ")"; + } + + /** + * Singleton {@link UnboundMethod}, performs no operation and returns null. + */ + private static UnboundMethod NOOP = new UnboundMethod(null, "NOOP") { + @Override + public <R> R invokeChecked(Object target, Object... args) throws Exception { + return null; + } + + @Override + public BoundMethod bind(Object receiver) { + return new BoundMethod(this, receiver); + } + + @Override + public StaticMethod asStatic() { + return new StaticMethod(this); + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public String toString() { + return "DynMethods.UnboundMethod(NOOP)"; + } + }; + } + + public static class BoundMethod { + private final UnboundMethod method; + private final Object receiver; + + private BoundMethod(UnboundMethod method, Object receiver) { + this.method = method; + this.receiver = receiver; + } + + public <R> R invokeChecked(Object... args) throws Exception { + return method.invokeChecked(receiver, args); + } + + public <R> R invoke(Object... args) { + return method.invoke(receiver, args); + } + } + + public static class StaticMethod { + private final UnboundMethod method; + + private StaticMethod(UnboundMethod method) { + this.method = method; + } + + public <R> R invokeChecked(Object... args) throws Exception { + return method.invokeChecked(null, args); + } + + public <R> R invoke(Object... args) { + return method.invoke(null, args); + } + } + + public static class Builder { + private final String name; + private ClassLoader loader = Thread.currentThread().getContextClassLoader(); + private UnboundMethod method = null; + + public Builder(String methodName) { + this.name = methodName; + } + + /** + * Set the {@link ClassLoader} used to lookup classes by name. + * <p> + * If not set, the current thread's ClassLoader is used. + * + * @param loader a ClassLoader + * @return this Builder for method chaining + */ + public Builder loader(ClassLoader loader) { + this.loader = loader; + return this; + } + + /** + * If no implementation has been found, adds a NOOP method. + * + * Note: calls to impl will not match after this method is called! + * + * @return this Builder for method chaining + */ + public Builder orNoop() { + if (method == null) { + this.method = UnboundMethod.NOOP; + } + return this; + } + + /** + * Checks for an implementation, first finding the given class by name. + * + * @param className name of a class + * @param methodName name of a method (different from constructor) + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder impl(String className, String methodName, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + Class<?> targetClass = Class.forName(className, true, loader); + impl(targetClass, methodName, argClasses); + } catch (ClassNotFoundException e) { + // not the right implementation + } + return this; + } + + /** + * Checks for an implementation, first finding the given class by name. + * + * The name passed to the constructor is the method name used. + * + * @param className name of a class + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder impl(String className, Class<?>... argClasses) { + impl(className, name, argClasses); + return this; + } + + /** + * Checks for a method implementation. + * + * @param methodName name of a method (different from constructor) + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder impl(Class<?> targetClass, String methodName, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + this.method = new UnboundMethod( + targetClass.getMethod(methodName, argClasses), name); + } catch (NoSuchMethodException e) { + // not the right implementation + } + return this; + } + + /** + * Checks for a method implementation. + * + * The name passed to the constructor is the method name used. + * + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder impl(Class<?> targetClass, Class<?>... argClasses) { + impl(targetClass, name, argClasses); + return this; + } + + public Builder ctorImpl(Class<?> targetClass, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + this.method = new DynConstructors.Builder() + .impl(targetClass, argClasses) + .buildChecked(); + } catch (NoSuchMethodException e) { + // not the right implementation + } + return this; + } + + public Builder ctorImpl(String className, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + this.method = new DynConstructors.Builder() + .impl(className, argClasses) + .buildChecked(); + } catch (NoSuchMethodException e) { + // not the right implementation + } + return this; + } + + /** + * Checks for an implementation, first finding the given class by name. + * + * @param className name of a class + * @param methodName name of a method (different from constructor) + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder hiddenImpl(String className, String methodName, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + Class<?> targetClass = Class.forName(className, true, loader); + hiddenImpl(targetClass, methodName, argClasses); + } catch (ClassNotFoundException e) { + // not the right implementation + } + return this; + } + + /** + * Checks for an implementation, first finding the given class by name. + * + * The name passed to the constructor is the method name used. + * + * @param className name of a class + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder hiddenImpl(String className, Class<?>... argClasses) { + hiddenImpl(className, name, argClasses); + return this; + } + + /** + * Checks for a method implementation. + * + * @param methodName name of a method (different from constructor) + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder hiddenImpl(Class<?> targetClass, String methodName, Class<?>... argClasses) { + // don't do any work if an implementation has been found + if (method != null) { + return this; + } + + try { + Method hidden = targetClass.getDeclaredMethod(methodName, argClasses); + AccessController.doPrivileged(new MakeAccessible(hidden)); + this.method = new UnboundMethod(hidden, name); + } catch (SecurityException e) { + // unusable + } catch (NoSuchMethodException e) { + // not the right implementation + } + return this; + } + + /** + * Checks for a method implementation. + * + * The name passed to the constructor is the method name used. + * + * @param argClasses argument classes for the method + * @return this Builder for method chaining + * @see {@link java.lang.Class#forName(String)} + * @see {@link java.lang.Class#getMethod(String, Class[])} + */ + public Builder hiddenImpl(Class<?> targetClass, Class<?>... argClasses) { + hiddenImpl(targetClass, name, argClasses); + return this; + } + + /** + * Returns the first valid implementation as a UnboundMethod or throws a + * NoSuchMethodException if there is none. + * + * @return a {@link UnboundMethod} with a valid implementation + * @throws NoSuchMethodException if no implementation was found + */ + public UnboundMethod buildChecked() throws NoSuchMethodException { + if (method != null) { + return method; + } else { + throw new NoSuchMethodException("Cannot find method: " + name); + } + } + + /** + * Returns the first valid implementation as a UnboundMethod or throws a + * RuntimeError if there is none. + * + * @return a {@link UnboundMethod} with a valid implementation + * @throws RuntimeException if no implementation was found + */ + public UnboundMethod build() { + if (method != null) { + return method; + } else { + throw new RuntimeException("Cannot find method: " + name); + } + } + + /** + * Returns the first valid implementation as a BoundMethod or throws a + * NoSuchMethodException if there is none. + * + * @param receiver an Object to receive the method invocation + * @return a {@link BoundMethod} with a valid implementation and receiver + * @throws IllegalStateException if the method is static + * @throws IllegalArgumentException if the receiver's class is incompatible + * @throws NoSuchMethodException if no implementation was found + */ + public BoundMethod buildChecked(Object receiver) throws NoSuchMethodException { + return buildChecked().bind(receiver); + } + + /** + * Returns the first valid implementation as a BoundMethod or throws a + * RuntimeError if there is none. + * + * @param receiver an Object to receive the method invocation + * @return a {@link BoundMethod} with a valid implementation and receiver + * @throws IllegalStateException if the method is static + * @throws IllegalArgumentException if the receiver's class is incompatible + * @throws RuntimeException if no implementation was found + */ + public BoundMethod build(Object receiver) { + return build().bind(receiver); + } + + /** + * Returns the first valid implementation as a StaticMethod or throws a + * NoSuchMethodException if there is none. + * + * @return a {@link StaticMethod} with a valid implementation + * @throws IllegalStateException if the method is not static + * @throws NoSuchMethodException if no implementation was found + */ + public StaticMethod buildStaticChecked() throws NoSuchMethodException { + return buildChecked().asStatic(); + } + + /** + * Returns the first valid implementation as a StaticMethod or throws a + * RuntimeException if there is none. + * + * @return a {@link StaticMethod} with a valid implementation + * @throws IllegalStateException if the method is not static + * @throws RuntimeException if no implementation was found + */ + public StaticMethod buildStatic() { + return build().asStatic(); + } + + } + + private static class MakeAccessible implements PrivilegedAction<Void> { + private Method hidden; + + public MakeAccessible(Method hidden) { + this.hidden = hidden; + } + + @Override + public Void run() { + hidden.setAccessible(true); + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/parquet-common/src/test/java/org/apache/parquet/TestUtils.java ---------------------------------------------------------------------- diff --git a/parquet-common/src/test/java/org/apache/parquet/TestUtils.java b/parquet-common/src/test/java/org/apache/parquet/TestUtils.java new file mode 100644 index 0000000..2062827 --- /dev/null +++ b/parquet-common/src/test/java/org/apache/parquet/TestUtils.java @@ -0,0 +1,70 @@ +/* + * 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.parquet; + +import org.junit.Assert; +import java.util.concurrent.Callable; + +public class TestUtils { + + /** + * A convenience method to avoid a large number of @Test(expected=...) tests + * @param message A String message to describe this assertion + * @param expected An Exception class that the Runnable should throw + * @param callable A Callable that is expected to throw the exception + */ + public static void assertThrows( + String message, Class<? extends Exception> expected, Callable callable) { + try { + callable.call(); + Assert.fail("No exception was thrown (" + message + "), expected: " + + expected.getName()); + } catch (Exception actual) { + try { + Assert.assertEquals(message, expected, actual.getClass()); + } catch (AssertionError e) { + e.addSuppressed(actual); + throw e; + } + } + } + + /** + * A convenience method to avoid a large number of @Test(expected=...) tests + * @param message A String message to describe this assertion + * @param expected An Exception class that the Runnable should throw + * @param runnable A Runnable that is expected to throw the runtime exception + */ + public static void assertThrows( + String message, Class<? extends Exception> expected, Runnable runnable) { + try { + runnable.run(); + Assert.fail("No exception was thrown (" + message + "), expected: " + + expected.getName()); + } catch (Exception actual) { + try { + Assert.assertEquals(message, expected, actual.getClass()); + } catch (AssertionError e) { + e.addSuppressed(actual); + throw e; + } + } + } +} http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/parquet-common/src/test/java/org/apache/parquet/util/Concatenator.java ---------------------------------------------------------------------- diff --git a/parquet-common/src/test/java/org/apache/parquet/util/Concatenator.java b/parquet-common/src/test/java/org/apache/parquet/util/Concatenator.java new file mode 100644 index 0000000..261c2be --- /dev/null +++ b/parquet-common/src/test/java/org/apache/parquet/util/Concatenator.java @@ -0,0 +1,82 @@ +/* + * 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.parquet.util; + +/** + * This is a class for testing DynMethods and DynConstructors. + */ +public class Concatenator { + public static class SomeCheckedException extends Exception { + } + + private String sep = ""; + + public Concatenator() { + } + + public Concatenator(String sep) { + this.sep = sep; + } + + private Concatenator(char sep) { + this.sep = String.valueOf(sep); + } + + public Concatenator(Exception e) throws Exception { + throw e; + } + + public static Concatenator newConcatenator(String sep) { + return new Concatenator(sep); + } + + private void setSeparator(String sep) { + this.sep = sep; + } + + public String concat(String left, String right) { + return left + sep + right; + } + + public String concat(String left, String middle, String right) { + return left + sep + middle + sep + right; + } + + public String concat(Exception e) throws Exception { + throw e; + } + + public String concat(String... strings) { + if (strings.length >= 1) { + StringBuilder sb = new StringBuilder(); + sb.append(strings[0]); + for (int i = 1; i < strings.length; i += 1) { + sb.append(sep); + sb.append(strings[i]); + } + return sb.toString(); + } + return null; + } + + public static String cat(String... strings) { + return new Concatenator().concat(strings); + } +} http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java ---------------------------------------------------------------------- diff --git a/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java b/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java new file mode 100644 index 0000000..1ab9582 --- /dev/null +++ b/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java @@ -0,0 +1,235 @@ +/* + * 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.parquet.util; + +import org.apache.parquet.TestUtils; +import org.apache.parquet.util.Concatenator.SomeCheckedException; +import org.junit.Assert; +import org.junit.Test; +import java.util.concurrent.Callable; + +public class TestDynConstructors { + @Test + public void testNoImplCall() { + final DynConstructors.Builder builder = new DynConstructors.Builder(); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + builder.build(); + } + }); + } + + @Test + public void testMissingClass() { + final DynConstructors.Builder builder = new DynConstructors.Builder() + .impl("not.a.RealClass"); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + builder.build(); + } + }); + } + + @Test + public void testMissingConstructor() { + final DynConstructors.Builder builder = new DynConstructors.Builder() + .impl(Concatenator.class, String.class, String.class); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + builder.build(); + } + }); + } + + @Test + public void testFirstImplReturned() throws Exception { + final DynConstructors.Ctor<Concatenator> sepCtor = new DynConstructors.Builder() + .impl("not.a.RealClass", String.class) + .impl(Concatenator.class, String.class) + .impl(Concatenator.class) + .buildChecked(); + + Concatenator dashCat = sepCtor.newInstanceChecked("-"); + Assert.assertEquals("Should construct with the 1-arg version", + "a-b", dashCat.concat("a", "b")); + + TestUtils.assertThrows("Should complain about extra arguments", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return sepCtor.newInstanceChecked("/", "-"); + } + }); + + TestUtils.assertThrows("Should complain about extra arguments", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return sepCtor.newInstance("/", "-"); + } + }); + + DynConstructors.Ctor<Concatenator> defaultCtor = new DynConstructors.Builder() + .impl("not.a.RealClass", String.class) + .impl(Concatenator.class) + .impl(Concatenator.class, String.class) + .buildChecked(); + + Concatenator cat = defaultCtor.newInstanceChecked(); + Assert.assertEquals("Should construct with the no-arg version", + "ab", cat.concat("a", "b")); + } + + @Test + public void testExceptionThrown() throws Exception { + final SomeCheckedException exc = new SomeCheckedException(); + final DynConstructors.Ctor<Concatenator> sepCtor = new DynConstructors.Builder() + .impl("not.a.RealClass", String.class) + .impl(Concatenator.class, Exception.class) + .buildChecked(); + + TestUtils.assertThrows("Should re-throw the exception", + SomeCheckedException.class, new Callable() { + @Override + public Object call() throws Exception { + return sepCtor.newInstanceChecked(exc); + } + }); + + TestUtils.assertThrows("Should wrap the exception in RuntimeException", + RuntimeException.class, new Callable() { + @Override + public Object call() throws Exception { + return sepCtor.newInstance(exc); + } + }); + } + + @Test + public void testStringClassname() throws Exception { + final DynConstructors.Ctor<Concatenator> sepCtor = new DynConstructors.Builder() + .impl(Concatenator.class.getName(), String.class) + .buildChecked(); + + Assert.assertNotNull("Should find 1-arg constructor", sepCtor.newInstance("-")); + } + + @Test + public void testHiddenMethod() throws Exception { + TestUtils.assertThrows("Should fail to find hidden method", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return new DynMethods.Builder("setSeparator") + .impl(Concatenator.class, char.class) + .buildChecked(); + } + }); + + final DynConstructors.Ctor<Concatenator> sepCtor = new DynConstructors.Builder() + .hiddenImpl(Concatenator.class.getName(), char.class) + .buildChecked(); + + Assert.assertNotNull("Should find hidden ctor with hiddenImpl", sepCtor); + + Concatenator slashCat = sepCtor.newInstanceChecked('/'); + + Assert.assertEquals("Should use separator /", + "a/b", slashCat.concat("a", "b")); + } + + @Test + public void testBind() throws Exception { + final DynConstructors.Ctor<Concatenator> ctor = new DynConstructors.Builder() + .impl(Concatenator.class.getName()) + .buildChecked(); + + Assert.assertTrue("Should always be static", ctor.isStatic()); + + TestUtils.assertThrows("Should complain that method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return ctor.bind(null); + } + }); + } + + @Test + public void testInvoke() throws Exception { + final DynMethods.UnboundMethod ctor = new DynConstructors.Builder() + .impl(Concatenator.class.getName()) + .buildChecked(); + + TestUtils.assertThrows("Should complain that target must be null", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return ctor.invokeChecked("a"); + } + }); + + TestUtils.assertThrows("Should complain that target must be null", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return ctor.invoke("a"); + } + }); + + Assert.assertNotNull("Should allow invokeChecked(null, ...)", + ctor.invokeChecked(null)); + Assert.assertNotNull("Should allow invoke(null, ...)", + ctor.invoke(null)); + } +} http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java ---------------------------------------------------------------------- diff --git a/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java b/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java new file mode 100644 index 0000000..7017c67 --- /dev/null +++ b/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java @@ -0,0 +1,410 @@ +/* + * 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.parquet.util; + +import org.apache.parquet.TestUtils; +import org.apache.parquet.util.Concatenator.SomeCheckedException; +import org.junit.Assert; +import org.junit.Test; +import java.util.concurrent.Callable; + +public class TestDynMethods { + @Test + public void testNoImplCall() { + final DynMethods.Builder builder = new DynMethods.Builder("concat"); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + builder.build(); + } + }); + } + + @Test + public void testMissingClass() { + final DynMethods.Builder builder = new DynMethods.Builder("concat") + .impl("not.a.RealClass", String.class, String.class); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + builder.build(); + } + }); + } + + @Test + public void testMissingMethod() { + final DynMethods.Builder builder = new DynMethods.Builder("concat") + .impl(Concatenator.class, "cat2strings", String.class, String.class); + + TestUtils.assertThrows("Checked build should throw NoSuchMethodException", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return builder.buildChecked(); + } + }); + + TestUtils.assertThrows("Normal build should throw RuntimeException", + RuntimeException.class, new Runnable() { + @Override + public void run() { + 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(); + + TestUtils.assertThrows("Should fail if non-string arguments are passed", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return cat.invoke(obj, 3, 4); + } + }); + + TestUtils.assertThrows("Should fail if non-string arguments are passed", + IllegalArgumentException.class, new Callable() { + @Override + public Object call() throws Exception { + return cat.invokeChecked(obj, 3, 4); + } + }); + } + + @Test + public void testExceptionThrown() throws Exception { + final SomeCheckedException exc = new 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(); + + TestUtils.assertThrows("Should re-throw the exception", + SomeCheckedException.class, new Callable() { + @Override + public Object call() throws Exception { + return cat.invokeChecked(obj, exc); + } + }); + + TestUtils.assertThrows("Should wrap the exception in RuntimeException", + RuntimeException.class, new Callable() { + @Override + public Object call() throws Exception { + return 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("-"); + + TestUtils.assertThrows("Should fail to find hidden method", + NoSuchMethodException.class, new Callable() { + @Override + public Object call() throws NoSuchMethodException { + return 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); + + TestUtils.assertThrows("Should complain that method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return builder.buildChecked(new Concatenator()); + } + }); + + TestUtils.assertThrows("Should complain that method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return builder.build(new Concatenator()); + } + }); + + final DynMethods.UnboundMethod staticCat = builder.buildChecked(); + Assert.assertTrue("Should be static", staticCat.isStatic()); + + TestUtils.assertThrows("Should complain that method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return 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); + + TestUtils.assertThrows("Should complain that method is not static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return builder.buildStatic(); + } + }); + + TestUtils.assertThrows("Should complain that method is not static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return builder.buildStaticChecked(); + } + }); + + final DynMethods.UnboundMethod cat2 = builder.buildChecked(); + Assert.assertFalse("concat(String,String) should not be static", + cat2.isStatic()); + + TestUtils.assertThrows("Should complain that method is not static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return 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 + TestUtils.assertThrows("Should complain that ctor method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return builder.buildChecked(new Concatenator()); + } + }); + TestUtils.assertThrows("Should complain that ctor method is static", + IllegalStateException.class, new Callable() { + @Override + public Object call() throws Exception { + return 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")); + } +} http://git-wip-us.apache.org/repos/asf/parquet-mr/blob/ddbeb4dd/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index df4bbd3..0d4df8a 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,12 @@ <avro.version>1.8.1</avro.version> <guava.version>20.0</guava.version> <mockito.version>1.10.19</mockito.version> + + <!-- parquet-cli dependencies --> + <opencsv.version>2.3</opencsv.version> + <jackson2.version>2.3.1</jackson2.version> + <jcommander.version>1.35</jcommander.version> + <commons-codec.version>1.10</commons-codec.version> </properties> <modules> @@ -97,6 +103,7 @@ <module>parquet-benchmarks</module> <module>parquet-cascading</module> <module>parquet-cascading3</module> + <module>parquet-cli</module> <module>parquet-column</module> <module>parquet-common</module> <module>parquet-encoding</module>