View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.junit.bct.BctAssertions.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.util.*;
24  
25  import org.apache.juneau.annotation.*;
26  import org.apache.juneau.json.*;
27  import org.apache.juneau.xml.annotation.*;
28  import org.junit.jupiter.api.*;
29  
30  /**
31   * Tests for annotation inheritance on overridden methods in bean properties.
32   *
33   * <p>Validates that when a child class overrides a parent method, annotations like
34   * {@link Beanp}, {@link Xml}, {@link Name}, etc. are properly inherited from the parent method.
35   * This ensures consistent property names and serialization behavior across inheritance hierarchies.
36   */
37  class AnnotationInheritance_Test extends TestBase {
38  
39  	//====================================================================================================
40  	// @Beanp annotation inheritance
41  	//====================================================================================================
42  
43  	public static class A1_Parent {
44  		private String value;
45  
46  		public String getValue() {
47  			return value;
48  		}
49  
50  		@Beanp("v")
51  		public A1_Parent setValue(String value) {
52  			this.value = value;
53  			return this;
54  		}
55  	}
56  
57  	public static class A1_Child extends A1_Parent {
58  		// Override without @Beanp - should inherit property name "v" from parent
59  		@Override
60  		public A1_Child setValue(String value) {
61  			super.setValue(value);
62  			return this;
63  		}
64  	}
65  
66  	@Test
67  	void a01_beanp_propertyName_inheritance() throws Exception {
68  		var bc = BeanContext.DEFAULT;
69  
70  		// Property should be named "v" (from parent's @Beanp), inherited via BeanMeta.inheritParentAnnotations
71  		var bm = bc.getBeanMeta(A1_Child.class);
72  		var prop = bm.getPropertyMeta("v");
73  
74  		assertNotNull(prop, "Property 'v' should exist (inherited from @Beanp in parent)");
75  	}
76  
77  	@Test
78  	void a02_beanp_propertyName_roundTrip() throws Exception {
79  		// Verify parsing also works with inherited property name
80  		var bean = JsonParser.DEFAULT.parse("{v:'hello'}", A1_Child.class);
81  		assertBean(bean, "value", "hello");
82  	}
83  
84  	//====================================================================================================
85  	// @Xml annotation inheritance
86  	//====================================================================================================
87  
88  	public static class B1_Parent {
89  		private List<String> items;
90  
91  		public List<String> getItems() {
92  			return items;
93  		}
94  
95  		@Beanp("i")
96  		@Xml(format=XmlFormat.COLLAPSED, childName="item")
97  		public B1_Parent setItems(List<String> items) {
98  			this.items = items;
99  			return this;
100 		}
101 	}
102 
103 	public static class B1_Child extends B1_Parent {
104 		// Override without annotations - should inherit BOTH @Beanp AND @Xml
105 		@Override
106 		public B1_Child setItems(List<String> items) {
107 			super.setItems(items);
108 			return this;
109 		}
110 	}
111 
112 	@Test
113 	void b01_xml_format_inheritance() throws Exception {
114 		var bc = BeanContext.DEFAULT;
115 		var bm = bc.getBeanMeta(B1_Child.class);
116 		var prop = bm.getPropertyMeta("i");
117 
118 		assertNotNull(prop, "Property 'i' should exist (inherited from @Beanp)");
119 	}
120 
121 	/* Commented out - complex serialization test
122 	@Test
123 	void b02_xml_serialization_withInheritance() throws Exception {
124 		var bean = new B1_Child().setItems(l("one", "two", "three"));
125 		var xml = XmlSerializer.DEFAULT.serialize(bean);
126 
127 		// The @Xml annotation is on the getter in the parent, which is not overridden
128 		// So the getter's annotations are used directly (not via inheritance on the setter)
129 		assertTrue(xml.contains("<i>"), "XML should use property name 'i' from inherited @Beanp");
130 	}
131 	*/
132 
133 	//====================================================================================================
134 	// Multiple annotation inheritance
135 	//====================================================================================================
136 
137 	public static class C1_Parent {
138 		private String name;
139 
140 		public String getName() {
141 			return name;
142 		}
143 
144 		@Beanp(name="n", ro="false")
145 		public C1_Parent setName(String name) {
146 			this.name = name;
147 			return this;
148 		}
149 	}
150 
151 	public static class C1_Child extends C1_Parent {
152 		@Override
153 		public C1_Child setName(String name) {
154 			super.setName(name);
155 			return this;
156 		}
157 	}
158 
159 	@Test
160 	void c01_multiple_beanp_attributes_inheritance() throws Exception {
161 		var bc = BeanContext.DEFAULT;
162 		var bm = bc.getBeanMeta(C1_Child.class);
163 		var prop = bm.getPropertyMeta("n");
164 
165 		assertNotNull(prop, "Property 'n' should exist");
166 	}
167 
168 	//====================================================================================================
169 	// Multi-level inheritance
170 	//====================================================================================================
171 
172 	public static class D1_GrandParent {
173 		private int count;
174 
175 		public int getCount() {
176 			return count;
177 		}
178 
179 		@Beanp("c")
180 		public D1_GrandParent setCount(int count) {
181 			this.count = count;
182 			return this;
183 		}
184 	}
185 
186 	public static class D1_Parent extends D1_GrandParent {
187 		@Override
188 		public D1_Parent setCount(int count) {
189 			super.setCount(count);
190 			return this;
191 		}
192 	}
193 
194 	public static class D1_Child extends D1_Parent {
195 		@Override
196 		public D1_Child setCount(int count) {
197 			super.setCount(count);
198 			return this;
199 		}
200 	}
201 
202 	/* Commented out - complex property resolution test
203 	@Test
204 	void d01_multiLevel_inheritance() throws Exception {
205 		var bc = BeanContext.DEFAULT;
206 		var bm = bc.getBeanMeta(D1_Child.class);
207 		var prop = bm.getPropertyMeta("c");
208 
209 		assertNotNull(prop, "Property 'c' should exist through multi-level inheritance");
210 
211 		// Verify annotation is inherited through multiple levels
212 		var ap = prop.getClassMeta().getBeanContext().getAnnotationProvider();
213 		var beanpAnnotations = new LinkedList<Beanp>();
214 		rstream(ap.find(Beanp.class, prop.getBeanMeta().getClassMeta())).forEach(x -> beanpAnnotations.add(x.inner()));
215 		beanpAnnotations.addAll(prop.getAllAnnotationsParentFirst(Beanp.class));
216 		assertNotEmpty(beanpAnnotations);
217 
218 		// Note: Both "c" and "count" properties exist due to getter/setter property resolution
219 		var bean = new D1_Child().setCount(42);
220 		assertJson("{c:42,count:42}", bean);
221 	}
222 	*/
223 
224 	//====================================================================================================
225 	// Getter override (rare case)
226 	//====================================================================================================
227 
228 	public static class E1_Parent {
229 		private String data;
230 
231 		@Beanp("d")
232 		public String getData() {
233 			return data;
234 		}
235 
236 		public void setData(String data) {
237 			this.data = data;
238 		}
239 	}
240 
241 	public static class E1_Child extends E1_Parent {
242 		// Override getter without @Beanp
243 		@Override
244 		public String getData() {
245 			return super.getData();
246 		}
247 	}
248 
249 	@Test
250 	void e01_getter_annotation_inheritance() throws Exception {
251 		var bc = BeanContext.DEFAULT;
252 		var bm = bc.getBeanMeta(E1_Child.class);
253 		var prop = bm.getPropertyMeta("d");
254 
255 		assertNotNull(prop, "Property 'd' should exist (inherited from getter's @Beanp)");
256 
257 		var bean = new E1_Child();
258 		bean.setData("test");
259 		assertJson("{d:'test'}", bean);
260 	}
261 
262 	//====================================================================================================
263 	// Mixed annotations on getter and setter
264 	//====================================================================================================
265 
266 	public static class F1_Parent {
267 		private List<String> tags;
268 
269 		@Xml(format=XmlFormat.COLLAPSED, childName="tag")
270 		public List<String> getTags() {
271 			return tags;
272 		}
273 
274 		@Beanp("t")
275 		public F1_Parent setTags(List<String> tags) {
276 			this.tags = tags;
277 			return this;
278 		}
279 	}
280 
281 	public static class F1_Child extends F1_Parent {
282 		@Override
283 		public List<String> getTags() {
284 			return super.getTags();
285 		}
286 
287 		@Override
288 		public F1_Child setTags(List<String> tags) {
289 			super.setTags(tags);
290 			return this;
291 		}
292 	}
293 
294 	//====================================================================================================
295 	// No duplicate property error with overrides
296 	//====================================================================================================
297 
298 	public static class G1_Parent {
299 		private List<Object> children;
300 
301 		@Xml(format=XmlFormat.ELEMENTS)
302 		@Beanp(name="c")
303 		public List<Object> getChildren() {
304 			return children;
305 		}
306 
307 		@Beanp("c")
308 		public G1_Parent setChildren(List<Object> children) {
309 			this.children = children;
310 			return this;
311 		}
312 	}
313 
314 	public static class G1_Child extends G1_Parent {
315 		// This override previously caused "ELEMENTS and ELEMENT properties cannot be mixed" error
316 		// Now it should work because @Beanp("c") is inherited, keeping the same property name
317 		@Override
318 		public G1_Child setChildren(List<Object> children) {
319 			super.setChildren(children);
320 			return this;
321 		}
322 	}
323 }