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>
