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


The following commit(s) were added to refs/heads/master by this push:
     new 3d5a340  JUNEAU-188
3d5a340 is described below

commit 3d5a3403ad79f96def9ed6571b3c39e607b3cdec
Author: JamesBognar <[email protected]>
AuthorDate: Sat Mar 7 13:15:19 2020 -0500

    JUNEAU-188
    
    @Bean annotation should override class visibility rules.
---
 .../java/org/apache/juneau/VisibilityTest.java     |   8 +-
 .../src/test/java/org/apache/juneau/a/A1.java      |   5 -
 .../juneau/annotation/BeanAnnotationTest.java      | 156 +++++++++++++++++++++
 .../src/main/java/org/apache/juneau/BeanMeta.java  |  55 +++++---
 .../java/org/apache/juneau/reflect/ClassInfo.java  |  26 ++++
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |  10 +-
 6 files changed, 230 insertions(+), 30 deletions(-)

diff --git 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
index e2d67dc..d00101e 100755
--- 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
+++ 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
@@ -26,10 +26,10 @@ public class VisibilityTest {
        
//====================================================================================================
        @Test
        public void testClassDefault() throws Exception {
-               JsonSerializerBuilder s1 = 
JsonSerializer.create().ssq().beansRequireSomeProperties(false);
-               JsonSerializerBuilder s2 = 
JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(PROTECTED);
-               JsonSerializerBuilder s3 = 
JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(Visibility.DEFAULT);
-               JsonSerializerBuilder s4 = 
JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(PRIVATE);
+               JsonSerializerBuilder s1 = 
JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false);
+               JsonSerializerBuilder s2 = 
JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(PROTECTED);
+               JsonSerializerBuilder s3 = 
JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(Visibility.DEFAULT);
+               JsonSerializerBuilder s4 = 
JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(PRIVATE);
 
                A1 a1 = A1.create();
                String r;
diff --git 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
index 8700f13..911b2ef 100755
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
@@ -16,7 +16,6 @@ import org.apache.juneau.annotation.*;
 
 // Default class
 @SuppressWarnings({"unused"})
-@Bean(sort=true)
 public class A1 {
        public int f1;
        protected int f2;
@@ -89,7 +88,6 @@ public class A1 {
                return x;
        }
 
-       @Bean(sort=true)
        public static class A2 {
                public int f1;
                protected int f2;
@@ -114,7 +112,6 @@ public class A1 {
                }
        }
 
-       @Bean(sort=true)
        protected static class A3 {
                public int f1;
                protected int f2;
@@ -139,7 +136,6 @@ public class A1 {
                }
        }
 
-       @Bean(sort=true)
        static class A4 {
                public int f1;
                protected int f2;
@@ -164,7 +160,6 @@ public class A1 {
                }
        }
 
-       @Bean(sort=true)
        private static class A5 {
                public int f1;
                protected int f2;
diff --git 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java
 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java
new file mode 100644
index 0000000..197d1e1
--- /dev/null
+++ 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java
@@ -0,0 +1,156 @@
+// 
***************************************************************************************************************************
+// * 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.annotation;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.reflect.*;
+import org.junit.*;
+
+public class BeanAnnotationTest {
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @Bean annotation overrides visibility rules on class and constructor.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Bean
+       @SuppressWarnings("unused")
+       private static class A1 {
+               public int f1;
+
+               public static A1 create() {
+                       A1 a = new A1();
+                       a.f1 = 1;
+                       return a;
+               }
+       }
+
+       @Test
+       public void testBeanAnnotationOverridesPrivate() throws Exception {
+               String json = SimpleJson.DEFAULT.toString(A1.create());
+               assertEquals("{f1:1}", json);
+               A1 a = SimpleJson.DEFAULT.read(json, A1.class);
+               json = SimpleJson.DEFAULT.toString(a);
+               assertEquals("{f1:1}", json);
+       }
+
+       @BeanConfig(applyBean=@Bean(on="A2"))
+       @SuppressWarnings("unused")
+       private static class A2 {
+               public int f1;
+
+               public static A2 create() {
+                       A2 a = new A2();
+                       a.f1 = 1;
+                       return a;
+               }
+       }
+       static ClassInfo a2ci = ClassInfo.of(A2.class);
+
+       @Test
+       public void testBeanAnnotationOverridesPrivate_usingConfig() throws 
Exception {
+               AnnotationList al = a2ci.getAnnotationList(null);
+               JsonSerializer js = 
JsonSerializer.create().simple().applyAnnotations(al, null).build();
+               JsonParser jp = JsonParser.create().applyAnnotations(al, 
null).build();
+
+               String json = js.serialize(A2.create());
+               assertEquals("{f1:1}", json);
+               A2 a = jp.parse(json, A2.class);
+               json = js.serialize(a);
+               assertEquals("{f1:1}", json);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @Beanc and @Beanp annotations overrides visibility rules on 
constructors/properties.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       public static class B1 {
+
+               @Beanp
+               private int f1;
+
+               private int f2;
+
+               @Beanp
+               private void setF2(int f2) {
+                       this.f2 = f2;
+               }
+
+               @Beanp
+               private int getF2() {
+                       return f2;
+               }
+
+               @Beanc
+               private B1() {}
+
+               public static B1 create() {
+                       B1 b = new B1();
+                       b.f1 = 1;
+                       b.f2 = 2;
+                       return b;
+               }
+       }
+
+       @Test
+       public void testBeanxAnnotationOverridesPrivate() throws Exception {
+               String json = SimpleJson.DEFAULT.toString(B1.create());
+               assertEquals("{f1:1,f2:2}", json);
+               B1 b = SimpleJson.DEFAULT.read(json, B1.class);
+               json = SimpleJson.DEFAULT.toString(b);
+               assertEquals("{f1:1,f2:2}", json);
+       }
+
+       
@BeanConfig(applyBeanc=@Beanc(on="B2()"),applyBeanp={@Beanp(on="B2.f1"),@Beanp(on="B2.setF2"),@Beanp(on="B2.getF2")})
+       @SuppressWarnings("unused")
+       public static class B2 {
+
+               private int f1;
+
+               private int f2;
+
+               private void setF2(int f2) {
+                       this.f2 = f2;
+               }
+
+               private int getF2() {
+                       return f2;
+               }
+
+               private B2() {}
+
+               public static B2 create() {
+                       B2 b = new B2();
+                       b.f1 = 1;
+                       b.f2 = 2;
+                       return b;
+               }
+       }
+       static ClassInfo b2ci = ClassInfo.of(B2.class);
+
+       @Test
+       public void testBeanxAnnotationOverridesPrivate_usingConfig() throws 
Exception {
+               AnnotationList al = b2ci.getAnnotationList(null);
+               JsonSerializer js = 
JsonSerializer.create().simple().applyAnnotations(al, null).build();
+               JsonParser jp = JsonParser.create().applyAnnotations(al, 
null).build();
+
+               String json = js.serialize(B2.create());
+               assertEquals("{f1:1,f2:2}", json);
+               B2 b = jp.parse(json, B2.class);
+               json = js.serialize(b);
+               assertEquals("{f1:1,f2:2}", json);
+       }
+}
+
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 808c056..eaf4769 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -21,6 +21,7 @@ import java.beans.BeanInfo;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
 import java.io.*;
+import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
 
@@ -198,21 +199,25 @@ public class BeanMeta<T> {
 
                                Map<String,BeanPropertyMeta.Builder> 
normalProps = new LinkedHashMap<>();
 
+                               Annotation ba = ci.findFirstAnnotation(ctx, 
Bean.class, BeanIgnore.class);
+                               boolean hasBean = ba != null && 
ba.annotationType() == Bean.class;
+                               boolean hasBeanIgnore = ba != null && 
ba.annotationType() == BeanIgnore.class;
+
                                /// See if this class matches one the patterns 
in the exclude-class list.
                                if (ctx.isNotABean(c))
                                        return "Class matches exclude-class 
list";
 
-                               if (! (cVis.isVisible(c.getModifiers()) || 
c.isAnonymousClass()))
+                               if (! hasBean && ! 
(cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
                                        return "Class is not public";
 
-                               if (isIgnored(c))
+                               if (hasBeanIgnore)
                                        return "Class is annotated with 
@BeanIgnore";
 
                                // Make sure it's serializable.
                                if (beanFilter == null && 
ctx.isBeansRequireSerializable() && ! ci.isChildOf(Serializable.class))
                                        return "Class is not serializable";
 
-                               // Look for @BeanConstructor constructor.
+                               // Look for @Beanc constructor on public 
constructors.
                                for (ConstructorInfo x : 
ci.getPublicConstructors()) {
                                        if 
(x.hasAnnotation(BeanConstructor.class)) {
                                                if (constructor != null)
@@ -254,12 +259,38 @@ public class BeanMeta<T> {
                                        }
                                }
 
+                               // Look for @Beanc on all other constructors.
+                               if (constructor == null) {
+                                       for (ConstructorInfo x : 
ci.getDeclaredConstructors()) {
+                                               if 
(ctx.hasAnnotation(Beanc.class, x)) {
+                                                       if (constructor != null)
+                                                               throw new 
BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");
+                                                       constructor = x;
+                                                       constructorArgs = 
split(ctx.getAnnotation(Beanc.class, x).properties());
+                                                       if 
(constructorArgs.length != x.getParamCount()) {
+                                                               if 
(constructorArgs.length != 0)
+                                                                       throw 
new BeanRuntimeException(c, "Number of properties defined in '@Beanc' 
annotation does not match number of parameters in constructor.");
+                                                               constructorArgs 
= new String[x.getParamCount()];
+                                                               int i = 0;
+                                                               for (ParamInfo 
pi : x.getParams()) {
+                                                                       String 
pn = pi.getName();
+                                                                       if (pn 
== null)
+                                                                               
throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of 
constructor ''{1}''", i, x.getFullName());
+                                                                       
constructorArgs[i++] = pn;
+                                                               }
+                                                       }
+                                                       
constructor.setAccessible();
+                                               }
+                                       }
+                               }
+
+
                                // If this is an interface, look for impl 
classes defined in the context.
                                if (constructor == null)
                                        constructor = 
ctx.getImplClassConstructor(c, conVis);
 
                                if (constructor == null)
-                                       constructor = 
ci.getNoArgConstructor(conVis);
+                                       constructor = 
ci.getNoArgConstructor(hasBean ? Visibility.PRIVATE : conVis);
 
                                if (constructor == null && beanFilter == null 
&& ctx.isBeansRequireDefaultConstructor())
                                        return "Class does not have the 
required no-arg constructor";
@@ -482,22 +513,6 @@ public class BeanMeta<T> {
                        return null;
                }
 
-               private boolean isIgnored(Class<?> c) {
-                       if (c == null)
-                               return false;
-                       if (ctx.hasDeclaredAnnotation(BeanIgnore.class, c))
-                               return true;
-                       if (ctx.hasDeclaredAnnotation(Bean.class, c))
-                               return false;
-                       for (Class<?> ci : c.getInterfaces()) {
-                               if (ctx.hasDeclaredAnnotation(BeanIgnore.class, 
ci))
-                                       return true;
-                               if (ctx.hasDeclaredAnnotation(Bean.class, ci))
-                                       return false;
-                       }
-                       return isIgnored(c.getSuperclass());
-               }
-
                private String findDictionaryName(ClassMeta<?> cm) {
                        BeanRegistry br = cm.getBeanRegistry();
                        if (br != null) {
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
index 4f591a2..0aced5d 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
@@ -1285,6 +1285,32 @@ public final class ClassInfo {
                return l;
        }
 
+       /**
+        * Searches up the parent hierarchy of this class for the first 
annotation in the list it finds.
+        *
+        * @param mp Metadata provider.
+        * @param annotations The annotations to search for.
+        * @return The first annotation found, or <jk>null</jk> if not found.
+        */
+       @SafeVarargs
+       public final Annotation findFirstAnnotation(MetaProvider mp, Class<? 
extends Annotation>...annotations) {
+               for (Class<? extends Annotation> ca : annotations) {
+                       Annotation x = getAnnotation(ca, mp);
+                       if (x != null)
+                               return x;
+               }
+               for (ClassInfo ci : getInterfaces()) {
+                       for (Class<? extends Annotation> ca : annotations) {
+                               Annotation x = ci.getAnnotation(ca, mp);
+                               if (x != null)
+                                       return x;
+                       }
+               }
+               ClassInfo ci = getParent();
+               return ci == null ? null : ci.findFirstAnnotation(mp, 
annotations);
+       }
+
+
        AnnotationList appendAnnotationList(AnnotationList m) {
                for (ClassInfo ci : getParents())
                        for (Annotation a : ci.c.getDeclaredAnnotations())
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html 
b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index 9008dc2..479d397 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -21,9 +21,17 @@
 <h5 class='topic w800'>juneau-marshall</h5>
 <ul class='spaced-list'>
        <li>
-               <ja>@BeanIgnore</ja> and <ja>@Bean</ja> annotations can 
alternately occur in parent class hierarchy.
+               {@link oaj.annotation.Bean @Bean} and {@link 
oaj.annotation.BeanIgnore @BeanIgnore} annotations can alternately occur in 
parent class hierarchy.
                The first one found dictates whether a class is ignored as a 
bean or not.
        <li>
+               Applying the {@link oaj.annotation.Bean @Bean} annotation on a 
class will now force non-public classes to be interpreted as beans.
+               For example, applying {@link oaj.annotation.Bean @Bean} to a 
<jk>private</jk> class will force it to be treated as a bean.
+               <br>
+               Also, if a public bean constructor cannot be found, the default 
constructor will be used 
+               regardless of it's visibility if the {@link oaj.annotation.Bean 
@Bean} annotation is on the class.
+       <li>
+               The <ja>@Beanc</ja> annotation can now be recognized and used 
on non-public constructors.
+       <li>
                Several bug fixes in the {@link HtmlSerializer} and {@link 
HtmlParser} classes around the handling of 
                collections and arrays of beans with 
<c><ja>@Bean</ja>(typeName)</c> annotations.
        <li>

Reply via email to