You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/03/07 18:15:35 UTC

[juneau] branch master updated: JUNEAU-188

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 <ja...@apache.org>
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>