This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git

commit 42be736240488c2c582567ccf65e992b31df4c2a
Author: JamesBognar <[email protected]>
AuthorDate: Fri Apr 12 11:33:48 2019 -0400

    [JUNEAU-104] Support for using named method parameters
---
 .../juneau/reflection/ExecutableInfoTest.java      | 505 +++++++++++++++++++++
 .../apache/juneau/reflection/ExecutorInfoTest.java | 133 ------
 .../java/org/apache/juneau/utils/BeanDiffTest.java | 110 +++++
 .../org/apache/juneau/reflect/ExecutableInfo.java  |  46 +-
 .../java/org/apache/juneau/utils/BeanDiff.java     | 231 ++++++++++
 juneau-doc/docs/ReleaseNotes/8.0.1.html            |   6 +
 6 files changed, 885 insertions(+), 146 deletions(-)

diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
new file mode 100644
index 0000000..b2f4817
--- /dev/null
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
@@ -0,0 +1,505 @@
+// 
***************************************************************************************************************************
+// * 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.juneau.reflection;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+import static org.junit.Assert.*;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.reflect.*;
+import org.junit.*;
+
+public class ExecutableInfoTest {
+
+       private static void check(String expected, Object o) {
+               assertEquals(expected, TO_STRING.apply(o));
+       }
+
+       private static final Function<Object,String> TO_STRING = new 
Function<Object,String>() {
+               @Override
+               public String apply(Object t) {
+                       if (t == null)
+                               return null;
+                       if (t instanceof List)
+                               return 
((List<?>)t).stream().map(this).collect(Collectors.joining(","));
+                       if (t instanceof Iterable)
+                               return 
StreamSupport.stream(((Iterable<?>)t).spliterator(), 
false).map(this).collect(Collectors.joining(","));
+                       if (t.getClass().isArray())
+                               return 
StreamSupport.stream(ArrayUtils.toList(t, Object.class).spliterator(), 
false).map(this).collect(Collectors.joining(","));
+                       if (t instanceof Annotation)
+                               return 
t.toString().replaceAll("\\@[^\\$]*\\$(.*)", "@$1");
+                       if (t instanceof Class)
+                               return ((Class<?>)t).getSimpleName();
+                       if (t instanceof Package)
+                               return ((Package)t).getName();
+                       if (t instanceof ClassInfo)
+                               return ((ClassInfo)t).getSimpleName();
+                       if (t instanceof MethodInfo)
+                               return 
((MethodInfo)t).getDeclaringClass().getSimpleName() + '.' + 
((MethodInfo)t).getShortName();
+                       if (t instanceof ConstructorInfo)
+                               return ((ConstructorInfo)t).getShortName();
+                       if (t instanceof FieldInfo)
+                               return 
((FieldInfo)t).getDeclaringClass().getSimpleName() + '.' + 
((FieldInfo)t).getName();
+                       if (t instanceof AnnotationInfo)
+                               return 
apply(((AnnotationInfo<?>)t).getAnnotation());
+                       if (t instanceof ParamInfo)
+                               return apply(((ParamInfo)t).toString());
+                       return t.toString();
+               }
+       };
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instantiation.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       static class A {
+               public A() {}
+               public void foo() {}
+       }
+       static ClassInfo a = ClassInfo.of(A.class);
+
+       @Test
+       public void isConstructor() {
+               assertTrue(a.getPublicConstructor().isConstructor());
+               assertFalse(a.getPublicMethod("foo").isConstructor());
+       }
+
+       @Test
+       public void getDeclaringClass() {
+               check("A", a.getPublicConstructor().getDeclaringClass());
+               check("A", a.getPublicMethod("foo").getDeclaringClass());
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Parameters
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       static class B {
+               public B() {}
+               public B(String s) {}
+               public void m() {}
+               public int m(String s) { return 0; }
+       }
+       static ClassInfo b = ClassInfo.of(B.class);
+       static ExecutableInfo
+               b_c1=b.getPublicConstructor(),
+               b_c2=b.getPublicConstructor(String.class),
+               b_m1=b.getPublicMethod("m"),
+               b_m2=b.getPublicMethod("m", String.class)
+       ;
+
+       @Test
+       public void getParamCount() {
+               assertEquals(0, b_c1.getParamCount());
+               assertEquals(1, b_c2.getParamCount());
+               assertEquals(0, b_m1.getParamCount());
+               assertEquals(1, b_m2.getParamCount());
+       }
+
+       @Test
+       public void hasParams() {
+               assertEquals(false, b_c1.hasParams());
+               assertEquals(true, b_c2.hasParams());
+               assertEquals(false, b_m1.hasParams());
+               assertEquals(true, b_m2.hasParams());
+       }
+
+       @Test
+       public void hasNoParams() {
+               assertEquals(true, b_c1.hasNoParams());
+               assertEquals(false, b_c2.hasNoParams());
+               assertEquals(true, b_m1.hasNoParams());
+               assertEquals(false, b_m2.hasNoParams());
+       }
+
+       @Test
+       public void hasNumParams() {
+               assertEquals(false, b_c1.hasNumParams(1));
+               assertEquals(true, b_c2.hasNumParams(1));
+               assertEquals(false, b_m1.hasNumParams(1));
+               assertEquals(true, b_m2.hasNumParams(1));
+       }
+
+       @Test
+       public void getParams() {
+               check("", b_c1.getParams());
+               check("B[0]", b_c2.getParams());
+               check("", b_m1.getParams());
+               check("m[0]", b_m2.getParams());
+       }
+
+       @Test
+       public void getParams_twice() {
+               check("", b_c1.getParams());
+               check("", b_c1.getParams());
+       }
+
+       @Test
+       public void getParam() {
+               check("B[0]", b_c2.getParam(0));
+               check("m[0]", b_m2.getParam(0));
+       }
+
+       @Test
+       public void getParam_nocache() {
+               ClassInfo b = ClassInfo.of(B.class);
+               check("B[0]", b.getPublicConstructor(String.class).getParam(0));
+               check("m[0]", b.getPublicMethod("m", String.class).getParam(0));
+       }
+
+       @Test
+       public void getParam_indexOutOfBounds() {
+               try {
+                       b_c1.getParam(0);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '0'.  No parameters.", 
e.getLocalizedMessage());
+               }
+               try {
+                       b_c2.getParam(-1);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '-1'.  Parameter count: 1", 
e.getLocalizedMessage());
+               }
+               try {
+                       b_c2.getParam(1);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '1'.  Parameter count: 1", 
e.getLocalizedMessage());
+               }
+       }
+
+       @Test
+       public void getParam_indexOutOfBounds_noCache() {
+               ClassInfo b = ClassInfo.of(B.class);
+               try {
+                       b.getPublicConstructor().getParam(0);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '0'.  No parameters.", 
e.getLocalizedMessage());
+               }
+               try {
+                       b.getPublicConstructor(String.class).getParam(-1);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '-1'.  Parameter count: 1", 
e.getLocalizedMessage());
+               }
+               try {
+                       b.getPublicConstructor(String.class).getParam(1);
+               } catch (IndexOutOfBoundsException e) {
+                       assertEquals("Invalid index '1'.  Parameter count: 1", 
e.getLocalizedMessage());
+               }
+       }
+
+       @Test
+       public void getParamTypes() {
+               check("", b_c1.getParamTypes());
+               check("String", b_c2.getParamTypes());
+               check("", b_m1.getParamTypes());
+               check("String", b_m2.getParamTypes());
+       }
+
+       @Test
+       public void getParamTypes_twice() {
+               check("", b_c1.getParamTypes());
+               check("", b_c1.getParamTypes());
+       }
+
+       static enum B1 {
+               FOO;
+       }
+
+       @Test
+       public void getParamTypes_enum() {
+               ClassInfo b1 = ClassInfo.of(B1.class);
+               check("B1(String,int)", b1.getDeclaredConstructors());
+               check("String,int", 
b1.getDeclaredConstructors().get(0).getParamTypes());
+       }
+
+       @Test
+       public void getParamType() {
+               check("String", b_c2.getParamType(0));
+               check("String", b_m2.getParamType(0));
+       }
+
+       @Test
+       public void getRawParamTypes() {
+               check("", b_c1.getRawParamTypes());
+               check("String", b_c2.getRawParamTypes());
+               check("", b_m1.getRawParamTypes());
+               check("String", b_m2.getRawParamTypes());
+       }
+
+       @Test
+       public void getRawParamType() {
+               check("String", b_c2.getRawParamType(0));
+               check("String", b_m2.getRawParamType(0));
+       }
+
+       @Test
+       public void getRawGenericParamType() {
+               check("String", b_c2.getRawGenericParamType(0));
+               check("String", b_m2.getRawGenericParamType(0));
+       }
+
+       @Test
+       public void getRawGenericParamTypes() {
+               check("", b_c1.getRawGenericParamTypes());
+               check("String", b_c2.getRawGenericParamTypes());
+               check("", b_m1.getRawGenericParamTypes());
+               check("String", b_m2.getRawGenericParamTypes());
+       }
+
+       @Test
+       public void getRawParameters() {
+               check("", b_c1.getRawParameters());
+               
assertTrue(b_c2.getRawParameters()[0].toString().startsWith("java.lang.String 
"));
+               check("", b_m1.getRawParameters());
+               
assertTrue(b_m2.getRawParameters()[0].toString().startsWith("java.lang.String 
"));
+       }
+
+       @Test
+       public void getRawParameter() {
+               
assertTrue(b_c2.getRawParameter(0).toString().startsWith("java.lang.String "));
+               
assertTrue(b_m2.getRawParameter(0).toString().startsWith("java.lang.String "));
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Annotations
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Documented
+       @Target({PARAMETER,METHOD,CONSTRUCTOR})
+       @Retention(RUNTIME)
+       @Inherited
+       public static @interface CA {}
+
+       static class C {
+               public C() {}
+               public C(@CA String foo) {}
+               public @CA C(int bar) {}
+               public void m() {}
+               public void m(@CA String foo) {}
+               public @CA void m(int bar) {}
+       }
+       static ClassInfo c = ClassInfo.of(C.class);
+       static ExecutableInfo
+               c_c1=c.getPublicConstructor(),
+               c_c2=c.getPublicConstructor(String.class),
+               c_c3=c.getPublicConstructor(int.class),
+               c_m1=c.getPublicMethod("m"),
+               c_m2=c.getPublicMethod("m", String.class),
+               c_m3=c.getPublicMethod("m", int.class)
+       ;
+
+       @Test
+       public void getParameterAnnotations() {
+               check("", c_c1.getParameterAnnotations());
+               check("@CA()", c_c2.getParameterAnnotations());
+               check("", c_c3.getParameterAnnotations());
+               check("", c_m1.getParameterAnnotations());
+               check("@CA()", c_m2.getParameterAnnotations());
+               check("", c_m3.getParameterAnnotations());
+       }
+
+       @Test
+       public void getParameterAnnotations_atIndex() {
+               check("@CA()", c_c2.getParameterAnnotations(0));
+               check("@CA()", c_m2.getParameterAnnotations(0));
+       }
+
+       @Test
+       public void hasAnnotation() {
+               assertFalse(c_c1.hasAnnotation(CA.class));
+               assertFalse(c_c2.hasAnnotation(CA.class));
+               assertTrue(c_c3.hasAnnotation(CA.class));
+               assertFalse(c_m1.hasAnnotation(CA.class));
+               assertFalse(c_m2.hasAnnotation(CA.class));
+               assertTrue(c_m3.hasAnnotation(CA.class));
+       }
+
+       @Test
+       public void getAnnotation() {
+               check(null, c_c1.getAnnotation(CA.class));
+               check(null, c_c2.getAnnotation(CA.class));
+               check("@CA()", c_c3.getAnnotation(CA.class));
+               check(null, c_m1.getAnnotation(CA.class));
+               check(null, c_m2.getAnnotation(CA.class));
+               check("@CA()", c_m3.getAnnotation(CA.class));
+       }
+
+       @Test
+       public void getAnnotation_nullArg() {
+               check(null, c_c3.getAnnotation(null));
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Exceptions
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       static class D {
+               public D() throws IOException {}
+               public void m() throws IOException {}
+       }
+       static ClassInfo d = ClassInfo.of(D.class);
+       static ExecutableInfo
+               d_c=d.getPublicConstructor(),
+               d_m=d.getPublicMethod("m")
+       ;
+
+       @Test
+       public void getExceptionTypes() {
+               check("IOException", d_c.getExceptionTypes());
+               check("IOException", d_m.getExceptionTypes());
+       }
+
+       @Test
+       public void getExceptionTypes_twice() {
+               check("IOException", d_c.getExceptionTypes());
+               check("IOException", d_c.getExceptionTypes());
+       }
+
+       @Test
+       public void getRawExceptionTypes() {
+               check("IOException", d_c.getRawExceptionTypes());
+               check("IOException", d_m.getRawExceptionTypes());
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Characteristics
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void isAll() {
+       }
+
+       @Test
+       public void isAny() {
+       }
+
+       @Test
+       public void hasArgs() {
+       }
+
+       @Test
+       public void hasFuzzyArgs() {
+       }
+
+       @Test
+       public void isDeprecated() {
+       }
+
+       @Test
+       public void isNotDeprecated() {
+       }
+
+       @Test
+       public void isAbstract() {
+       }
+
+       @Test
+       public void isNotAbstract() {
+       }
+
+       @Test
+       public void isPublic() {
+       }
+
+       @Test
+       public void isNotPublic() {
+       }
+
+       @Test
+       public void isStatic() {
+       }
+
+       @Test
+       public void isNotStatic() {
+       }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Labels
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       static class X {
+               public X() {}
+               public X(String foo) {}
+               public X(Map<String,Object> foo) {}
+               public void foo(){}
+               public void foo(String foo){}
+               public void foo(Map<String,Object> foo){}
+       }
+       ClassInfo x = ClassInfo.of(X.class);
+
+       @Test
+       public void getFullName_method() {
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X.foo()", 
x.getPublicMethod("foo").getFullName());
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X.foo(java.lang.String)",
 x.getPublicMethod("foo", String.class).getFullName());
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X.foo(java.util.Map<java.lang.String,java.lang.Object>)",
 x.getPublicMethod("foo", Map.class).getFullName());
+       }
+
+       @Test
+       public void getFullName_constructor() {
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X()", 
x.getPublicConstructor().getFullName());
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X(java.lang.String)",
 x.getPublicConstructor(String.class).getFullName());
+               
assertEquals("org.apache.juneau.reflection.ExecutableInfoTest$X(java.util.Map<java.lang.String,java.lang.Object>)",
 x.getPublicConstructor(Map.class).getFullName());
+       }
+
+       @Test
+       public void getShortName_method() {
+               assertEquals("foo()", x.getPublicMethod("foo").getShortName());
+               assertEquals("foo(String)", x.getPublicMethod("foo", 
String.class).getShortName());
+               assertEquals("foo(Map)", x.getPublicMethod("foo", 
Map.class).getShortName());
+       }
+
+       @Test
+       public void getShortName_constructor() {
+               assertEquals("X()", x.getPublicConstructor().getShortName());
+               assertEquals("X(String)", 
x.getPublicConstructor(String.class).getShortName());
+               assertEquals("X(Map)", 
x.getPublicConstructor(Map.class).getShortName());
+       }
+
+       @Test
+       public void getSimpleName_method() {
+               assertEquals("foo", x.getPublicMethod("foo").getSimpleName());
+               assertEquals("foo", x.getPublicMethod("foo", 
String.class).getSimpleName());
+               assertEquals("foo", x.getPublicMethod("foo", 
Map.class).getSimpleName());
+       }
+
+       @Test
+       public void getSimpleName_constructor() {
+               assertEquals("X", x.getPublicConstructor().getSimpleName());
+               assertEquals("X", 
x.getPublicConstructor(String.class).getSimpleName());
+               assertEquals("X", 
x.getPublicConstructor(Map.class).getSimpleName());
+       }
+}
diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutorInfoTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutorInfoTest.java
deleted file mode 100644
index b859b51..0000000
--- 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/reflection/ExecutorInfoTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// 
***************************************************************************************************************************
-// * 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.juneau.reflection;
-
-import static org.junit.Assert.*;
-
-import java.util.*;
-
-import org.apache.juneau.reflect.*;
-import org.junit.*;
-
-public class ExecutorInfoTest {
-//
-//     private static void check(String expected, Object o) {
-//             if (o instanceof List) {
-//                     List<?> l = (List<?>)o;
-//                     String actual = l
-//                             .stream()
-//                             .map(TO_STRING)
-//                             .collect(Collectors.joining(","));
-//                     assertEquals(expected, actual);
-//             } else if (o instanceof Iterable) {
-//                     String actual = 
StreamSupport.stream(((Iterable<?>)o).spliterator(), false)
-//                             .map(TO_STRING)
-//                             .collect(Collectors.joining(","));
-//                     assertEquals(expected, actual);
-//             } else {
-//                     assertEquals(expected, TO_STRING.apply(o));
-//             }
-//     }
-//
-//     private static final Function<Object,String> TO_STRING = new 
Function<Object,String>() {
-//             @Override
-//             public String apply(Object t) {
-//                     if (t == null)
-//                             return null;
-//                     if (t instanceof Class)
-//                             return ((Class<?>)t).getSimpleName();
-//                     if (t instanceof Constructor) {
-//                             Constructor<?> x = (Constructor<?>)t;
-//                             return x.getDeclaringClass().getSimpleName() + 
'(' + argTypes(x.getParameterTypes()) + ')';
-//                     }
-////                   if (t instanceof Package)
-////                           return ((Package)t).getName();
-//                     if (t instanceof ClassInfo)
-//                             return ((ClassInfo)t).getSimpleName();
-////                   if (t instanceof MethodInfo)
-////                           return 
((MethodInfo)t).getDeclaringClass().getSimpleName() + '.' + 
((MethodInfo)t).getLabel();
-////                   if (t instanceof ConstructorInfo)
-////                           return ((ConstructorInfo)t).getLabel();
-////                   if (t instanceof FieldInfo)
-////                           return 
((FieldInfo)t).getDeclaringClass().getSimpleName() + '.' + 
((FieldInfo)t).getLabel();
-////                   if (t instanceof AnnotationInfo)
-////                           return 
apply(((AnnotationInfo<?>)t).getAnnotation());
-//                     return t.toString();
-//             }
-//     };
-//
-//     private static String argTypes(Class<?>[] t) {
-//             return Arrays.asList(t).stream().map(x -> 
x.getSimpleName()).collect(Collectors.joining(","));
-//     }
-//
-
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Instantiation.
-       
//-----------------------------------------------------------------------------------------------------------------
-
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Labels
-       
//-----------------------------------------------------------------------------------------------------------------
-
-       static class X {
-               public X() {}
-               public X(String foo) {}
-               public X(Map<String,Object> foo) {}
-               public void foo(){}
-               public void foo(String foo){}
-               public void foo(Map<String,Object> foo){}
-       }
-       ClassInfo x = ClassInfo.of(X.class);
-
-       @Test
-       public void getFullName_method() {
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X.foo()", 
x.getPublicMethod("foo").getFullName());
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X.foo(java.lang.String)",
 x.getPublicMethod("foo", String.class).getFullName());
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X.foo(java.util.Map<java.lang.String,java.lang.Object>)",
 x.getPublicMethod("foo", Map.class).getFullName());
-       }
-
-       @Test
-       public void getFullName_constructor() {
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X()", 
x.getPublicConstructor().getFullName());
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X(java.lang.String)",
 x.getPublicConstructor(String.class).getFullName());
-               
assertEquals("org.apache.juneau.reflection.ExecutorInfoTest$X(java.util.Map<java.lang.String,java.lang.Object>)",
 x.getPublicConstructor(Map.class).getFullName());
-       }
-
-       @Test
-       public void getShortName_method() {
-               assertEquals("foo()", x.getPublicMethod("foo").getShortName());
-               assertEquals("foo(String)", x.getPublicMethod("foo", 
String.class).getShortName());
-               assertEquals("foo(Map)", x.getPublicMethod("foo", 
Map.class).getShortName());
-       }
-
-       @Test
-       public void getShortName_constructor() {
-               assertEquals("X()", x.getPublicConstructor().getShortName());
-               assertEquals("X(String)", 
x.getPublicConstructor(String.class).getShortName());
-               assertEquals("X(Map)", 
x.getPublicConstructor(Map.class).getShortName());
-       }
-
-       @Test
-       public void getSimpleName_method() {
-               assertEquals("foo", x.getPublicMethod("foo").getSimpleName());
-               assertEquals("foo", x.getPublicMethod("foo", 
String.class).getSimpleName());
-               assertEquals("foo", x.getPublicMethod("foo", 
Map.class).getSimpleName());
-       }
-
-       @Test
-       public void getSimpleName_constructor() {
-               assertEquals("X", x.getPublicConstructor().getSimpleName());
-               assertEquals("X", 
x.getPublicConstructor(String.class).getSimpleName());
-               assertEquals("X", 
x.getPublicConstructor(Map.class).getSimpleName());
-       }
-}
diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/BeanDiffTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/BeanDiffTest.java
new file mode 100644
index 0000000..eff4e90
--- /dev/null
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/BeanDiffTest.java
@@ -0,0 +1,110 @@
+// 
***************************************************************************************************************************
+// * 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.juneau.utils;
+
+import static org.junit.Assert.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+
+public class BeanDiffTest {
+
+       public static class A {
+               public int f1;
+               public String f2;
+
+               static A create(int f1, String f2) {
+                       A a = new A();
+                       a.f1 = f1;
+                       a.f2 = f2;
+                       return a;
+               }
+       }
+
+       @Test
+       public void testSame() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, "a"), A.create(1, 
"a")).build();
+               assertFalse(bd.hasDiffs());
+               assertEquals("{v1:{},v2:{}}", bd.toString());
+       }
+
+       @Test
+       public void testDifferent() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, "a"), A.create(2, 
"b")).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1,f2:'a'},v2:{f1:2,f2:'b'}}", 
bd.toString());
+       }
+
+       @Test
+       public void testFirstNull() throws Exception {
+               BeanDiff bd = BeanDiff.create(null, A.create(2, "b")).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{},v2:{f1:2,f2:'b'}}", bd.toString());
+       }
+
+       @Test
+       public void testSecondNull() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, "a"), null).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1,f2:'a'},v2:{}}", bd.toString());
+       }
+
+       @Test
+       public void testBothNull() throws Exception {
+               BeanDiff bd = BeanDiff.create(null, null).build();
+               assertFalse(bd.hasDiffs());
+               assertEquals("{v1:{},v2:{}}", bd.toString());
+       }
+
+       @Test
+       public void testNullFields() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2,f2:'b'}}", bd.toString());
+       }
+
+       @Test
+       public void testIncludes() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).include("f1").build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2}}", bd.toString());
+       }
+
+       @Test
+       public void testIncludesSet() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).include(ASet.<String>create("f1")).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2}}", bd.toString());
+       }
+
+       @Test
+       public void testExcludes() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).exclude("f2").build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2}}", bd.toString());
+       }
+
+       @Test
+       public void testExcludesSet() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).exclude(ASet.<String>create("f2")).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2}}", bd.toString());
+       }
+
+       @Test
+       public void testDifferentBeanContext() throws Exception {
+               BeanDiff bd = BeanDiff.create(A.create(1, null), A.create(2, 
"b")).beanContext(BeanContext.DEFAULT_SORTED).build();
+               assertTrue(bd.hasDiffs());
+               assertEquals("{v1:{f1:1},v2:{f1:2,f2:'b'}}", bd.toString());
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
index 79edea6..06b93e0 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
@@ -12,6 +12,8 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.reflect;
 
+import static org.apache.juneau.internal.StringUtils.*;
+
 import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
@@ -147,6 +149,7 @@ public abstract class ExecutableInfo {
         * @return The parameter information, never <jk>null</jk>.
         */
        public final ParamInfo getParam(int index) {
+               checkIndex(index);
                if (params != null)
                        return params.get(index);
                return new ParamInfo(this, rawParameters()[index], index);
@@ -159,13 +162,15 @@ public abstract class ExecutableInfo {
         */
        public final List<ClassInfo> getParamTypes() {
                if (paramTypes == null) {
-                       // Note that due to a bug involving Enum constructors, 
getGenericParameterTypes() may
-                       // always return an empty array.
                        Class<?>[] ptc = rawParamTypes();
+                       // Note that due to a bug involving Enum constructors, 
getGenericParameterTypes() may
+                       // always return an empty array.  This appears to be 
fixed in Java 8 b75.
                        Type[] ptt = rawGenericParamTypes();
+                       if (ptt.length != ptc.length)
+                               ptt = ptc;
                        List<ClassInfo> l = new ArrayList<>(ptc.length);
                        for (int i = 0; i < ptc.length; i++)
-                               l.add(ClassInfo.of(ptc[i], ptt.length > i ? 
ptt[i] : ptc[i]));
+                               l.add(ClassInfo.of(ptc[i], ptt[i]));
                        paramTypes = Collections.unmodifiableList(l);
                }
                return paramTypes;
@@ -178,6 +183,7 @@ public abstract class ExecutableInfo {
         * @return The parameter type of the parameter at the specified index.
         */
        public final ClassInfo getParamType(int index) {
+               checkIndex(index);
                if (paramTypes != null)
                        return getParamTypes().get(index);
                return ClassInfo.of(getRawParamType(index), 
getRawGenericParamType(index));
@@ -199,6 +205,7 @@ public abstract class ExecutableInfo {
         * @return The raw parameter type of the parameter at the specified 
index.
         */
        public final Class<?> getRawParamType(int index) {
+               checkIndex(index);
                return rawParamTypes()[index];
        }
 
@@ -212,6 +219,17 @@ public abstract class ExecutableInfo {
        }
 
        /**
+        * Returns the raw generic parameter type of the parameter at the 
specified index.
+        *
+        * @param index The parameter index.
+        * @return The raw generic parameter type of the parameter at the 
specified index.
+        */
+       public final Type getRawGenericParamType(int index) {
+               checkIndex(index);
+               return rawGenericParamTypes()[index];
+       }
+
+       /**
         * Returns an array of raw {@link Parameter} objects that represent all 
the parameters to the underlying executable represented by this object.
         *
         * @return An array of raw {@link Parameter} objects, or an empty array 
if executable has no parameters.
@@ -229,19 +247,10 @@ public abstract class ExecutableInfo {
         * @see Executable#getParameters()
         */
        public final Parameter getRawParameter(int index) {
+               checkIndex(index);
                return rawParameters()[index];
        }
 
-       /**
-        * Returns the raw generic parameter type of the parameter at the 
specified index.
-        *
-        * @param index The parameter index.
-        * @return The raw generic parameter type of the parameter at the 
specified index.
-        */
-       public final Type getRawGenericParamType(int index) {
-               return rawGenericParamTypes()[index];
-       }
-
        Class<?>[] rawParamTypes() {
                if (rawParamTypes == null)
                        rawParamTypes = e.getParameterTypes();
@@ -260,6 +269,14 @@ public abstract class ExecutableInfo {
                return rawParameters;
        }
 
+       private void checkIndex(int index) {
+               int pc = getParamCount();
+               if (pc == 0)
+                       throw new IndexOutOfBoundsException(format("Invalid 
index ''{0}''.  No parameters.", index));
+               if (index < 0 || index >= pc)
+                       throw new IndexOutOfBoundsException(format("Invalid 
index ''{0}''.  Parameter count: {1}", index, pc));
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Annotations
        
//-----------------------------------------------------------------------------------------------------------------
@@ -280,6 +297,7 @@ public abstract class ExecutableInfo {
         * @return The parameter annotations on the parameter at the specified 
index.
         */
        public final Annotation[] getParameterAnnotations(int index) {
+               checkIndex(index);
                return e.getParameterAnnotations()[index];
        }
 
@@ -311,6 +329,8 @@ public abstract class ExecutableInfo {
         */
        @SuppressWarnings("unchecked")
        public final <T extends Annotation> T getAnnotation(Class<T> a) {
+               if (a == null)
+                       return null;
                Optional<Annotation> o = annotationMap().get(a);
                if (o == null) {
                        o = Optional.ofNullable(findAnnotation(a));
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanDiff.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanDiff.java
new file mode 100644
index 0000000..eeb792b
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanDiff.java
@@ -0,0 +1,231 @@
+// 
***************************************************************************************************************************
+// * 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.juneau.utils;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.Bean;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.marshall.SimpleJson;
+
+/**
+ * Utility class for comparing two versions of a POJO.
+ *
+ * <p class='bpcode w800'>
+ *     <jc>// Two beans to compare.</jc>
+ *     MyBean bean1, bean2;
+ *
+ *     <jc>// Get differences.</jc>
+ *     BeanDiff bf = BeanDiff.<jsm>create</jsm>(bean1, 
bean2).exclude(<js>"fooProperty"</js>).build();
+ *
+ *     <jc>// Check for differences.</jc>
+ *     <jk>boolean</jk> b = bf.hasDiffs();
+ *
+ *     ObjectMap v1 = bf.getV1();  <jc>// Get version 1 differences.</jc>
+ *     ObjectMap v2 = bf.getV2();  <jc>// Get version 2 differences.</jc>
+ *
+ *     <jc>// Display differences.</jc>
+ *     System.<jsf>err</jsf>.println(bf);
+ * </p>
+ */
+@Bean(properties="v1,v2")
+public class BeanDiff {
+
+       private ObjectMap v1 = new ObjectMap(), v2 = new ObjectMap();
+
+       /**
+        * Constructor.
+        *
+        * @param bc The bean context to use for comparing beans.
+        * @param first The first bean to compare.
+        * @param second The second bean to compare.
+        * @param include
+        *      Optional properties to include in the comparison.
+        *      <br>If <jk>null</jk>, all properties are included.
+        * @param exclude
+        *      Optional properties to exclude in the comparison.
+        *      <br>If <jk>null</jk>, no properties are excluded.
+        */
+       @SuppressWarnings("null")
+       public <T> BeanDiff(BeanContext bc, T first, T second, Set<String> 
include, Set<String> exclude) {
+               if (first == null && second == null)
+                       return;
+               BeanSession bs = bc.createBeanSession();
+               BeanMap<?> bm1 = first == null ? null : bs.toBeanMap(first);
+               BeanMap<?> bm2 = second == null ? null : bs.toBeanMap(second);
+               Set<String> keys = bm1 != null ? bm1.keySet() : bm2.keySet();
+               for (String k : keys) {
+                       if ((include == null || include.contains(k)) && 
(exclude == null || ! exclude.contains(k))) {
+                               Object o1 = bm1 == null ? null : bm1.get(k);
+                               Object o2 = bm2 == null ? null : bm2.get(k);
+                               if (! ObjectUtils.equals(o1, o2)) {
+                                       if (o1 != null)
+                                               v1.put(k, o1);
+                                       if (o2 != null)
+                                               v2.put(k, o2);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Create a new builder for this class.
+        *
+        * @param first The first bean to compare.
+        * @param second The second bean to compare.
+        * @return A new builder.
+        */
+       public static <T> Builder<T> create(T first, T second) {
+               return new Builder<T>().first(first).second(second);
+       }
+
+       /**
+        * Builder class.
+        *
+        * @param <T> The bean type.
+        */
+       public static class Builder<T> {
+               T first, second;
+               BeanContext beanContext = BeanContext.DEFAULT;
+               Set<String> include, exclude;
+
+               /**
+                * Specifies the first bean to compare.
+                *
+                * @param value The first bean to compare.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> first(T value) {
+                       this.first = value;
+                       return this;
+               }
+
+               /**
+                * Specifies the second bean to compare.
+                *
+                * @param value The first bean to compare.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> second(T value) {
+                       this.second = value;
+                       return this;
+               }
+
+               /**
+                * Specifies the bean context to use for introspecting beans.
+                *
+                * <p>
+                * If not specified, uses {@link BeanContext#DEFAULT}.
+                *
+                * @param value The bean context to use for introspecting beans.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> beanContext(BeanContext value) {
+                       this.beanContext = value;
+                       return this;
+               }
+
+               /**
+                * Specifies the properties to include in the comparison.
+                *
+                * <p>
+                * If not specified, compares all properties.
+                *
+                * @param properties The properties to include in the 
comparison.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> include(String...properties) {
+                       include = new HashSet<>(Arrays.asList(properties));
+                       return this;
+               }
+
+               /**
+                * Specifies the properties to include in the comparison.
+                *
+                * <p>
+                * If not specified, compares all properties.
+                *
+                * @param properties The properties to include in the 
comparison.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> include(Set<String> properties) {
+                       include = properties;
+                       return this;
+               }
+
+               /**
+                * Specifies the properties to exclude from the comparison.
+                *
+                * @param properties The properties to exclude from the 
comparison.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> exclude(String...properties) {
+                       exclude = new HashSet<>(Arrays.asList(properties));
+                       return this;
+               }
+
+               /**
+                * Specifies the properties to exclude from the comparison.
+                *
+                * @param properties The properties to exclude from the 
comparison.
+                * @return This object (for method chaining).
+                */
+               public Builder<T> exclude(Set<String> properties) {
+                       exclude = properties;
+                       return this;
+               }
+
+               /**
+                * Build the differences.
+                *
+                * @return A new {@link BeanDiff} object.
+                */
+               public BeanDiff build() {
+                       return new BeanDiff(beanContext, first, second, 
include, exclude);
+               }
+
+       }
+
+       /**
+        * Returns <jk>true</jk> if the beans had differences.
+        *
+        * @return <jk>true</jk> if the beans had differences.
+        */
+       public boolean hasDiffs() {
+               return v1.size() > 0 || v2.size() > 0;
+       }
+
+       /**
+        * Returns the differences in the first bean.
+        *
+        * @return The differences in the first bean.
+        */
+       public ObjectMap getV1() {
+               return v1;
+       }
+
+       /**
+        * Returns the differences in the second bean.
+        *
+        * @return The differences in the second bean.
+        */
+       public ObjectMap getV2() {
+               return v2;
+       }
+
+       @Override
+       public String toString() {
+               return SimpleJson.DEFAULT.toString(this);
+       }
+}
diff --git a/juneau-doc/docs/ReleaseNotes/8.0.1.html 
b/juneau-doc/docs/ReleaseNotes/8.0.1.html
index 25394a7..36e1acf 100644
--- a/juneau-doc/docs/ReleaseNotes/8.0.1.html
+++ b/juneau-doc/docs/ReleaseNotes/8.0.1.html
@@ -19,6 +19,12 @@
        TBD
 </p>
 
+<h5 class='topic w800'>juneau-marshall</h5>
+<ul class='spaced-list'>
+       <li>
+               New utility class for diffing beans: {@link oaj.utils.BeanDiff}
+</ul>
+
 <h5 class='topic w800'>juneau-config</h5>
 <ul class='spaced-list'>
        <li>

Reply via email to