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.commons.annotation;
18  
19  import static org.apache.juneau.junit.bct.BctAssertions.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.lang.annotation.*;
23  
24  import org.apache.juneau.*;
25  import org.apache.juneau.commons.reflect.*;
26  import org.junit.jupiter.api.*;
27  
28  class AppliedOnClassAnnotationObject_Test extends TestBase {
29  
30  	private static final String CNAME = AppliedOnClassAnnotationObject_Test.class.getName();
31  
32  	//------------------------------------------------------------------------------------------------------------------
33  	// Test annotation for testing purposes
34  	//------------------------------------------------------------------------------------------------------------------
35  
36  	@Target({ ElementType.TYPE })
37  	@Retention(RetentionPolicy.RUNTIME)
38  	public @interface TA {
39  		String[] on() default {};
40  
41  		Class<?>[] onClass() default {};
42  
43  		String value() default "";
44  	}
45  
46  	/**
47  	 * Implementation of TA using AppliedOnClassAnnotationObject
48  	 */
49  	public static class T extends AppliedOnClassAnnotationObject implements TA {
50  
51  		private final String value;
52  
53  		public static class Builder extends AppliedAnnotationObject.BuilderT {
54  			String value = "";
55  
56  			public Builder() {
57  				super(TA.class);
58  			}
59  
60  			public Builder value(String value) {
61  				this.value = value;
62  				return this;
63  			}
64  
65  			@Override
66  			public Builder on(String...value) {
67  				super.on(value);
68  				return this;
69  			}
70  
71  			@Override
72  			public Builder on(Class<?>...value) {
73  				super.on(value);
74  				return this;
75  			}
76  
77  			@Override
78  			public Builder on(ClassInfo...value) {
79  				super.on(value);
80  				return this;
81  			}
82  
83  			@Override
84  			public Builder onClass(Class<?>...value) {
85  				super.onClass(value);
86  				return this;
87  			}
88  
89  			@Override
90  			public Builder onClass(ClassInfo...value) {
91  				super.onClass(value);
92  				return this;
93  			}
94  
95  			public TA build() {
96  				return new T(this);
97  			}
98  		}
99  
100 		public static Builder create() {
101 			return new Builder();
102 		}
103 
104 		public T(Builder b) {
105 			super(b);
106 			value = b.value;
107 		}
108 
109 		@Override
110 		public String value() {
111 			return value;
112 		}
113 	}
114 
115 	//------------------------------------------------------------------------------------------------------------------
116 	// Test classes for targeting
117 	//------------------------------------------------------------------------------------------------------------------
118 
119 	public static class TC1 {}
120 
121 	public static class TC2 {}
122 
123 	public static class TC3 {}
124 
125 	//------------------------------------------------------------------------------------------------------------------
126 	// Basic tests
127 	//------------------------------------------------------------------------------------------------------------------
128 
129 	@Test
130 	void a01_basic_noOnClass() {
131 		var a = T.create().build();
132 		assertList(a.onClass());
133 	}
134 
135 	@Test
136 	void a02_basic_singleClass() {
137 		var a = T.create().onClass(TC1.class).build();
138 
139 		assertList(a.onClass(), TC1.class);
140 	}
141 
142 	@Test
143 	void a03_basic_multipleClasses() {
144 		var a = T.create().onClass(TC1.class, TC2.class).build();
145 
146 		assertList(a.onClass(), TC1.class, TC2.class);
147 	}
148 
149 	@Test
150 	void a04_basic_withValue() {
151 		var a = T.create().onClass(TC1.class).value("test").build();
152 
153 		assertList(a.onClass(), TC1.class);
154 		assertEquals("test", a.value());
155 	}
156 
157 	@Test
158 	void a05_basic_annotationType() {
159 		var a = T.create().build();
160 		assertEquals(TA.class, a.annotationType());
161 	}
162 
163 	//------------------------------------------------------------------------------------------------------------------
164 	// on() vs onClass() tests
165 	//------------------------------------------------------------------------------------------------------------------
166 
167 	@Test
168 	void b01_onVsOnClass_onClassOnly() {
169 		var a = T.create().onClass(TC1.class, TC2.class).build();
170 
171 		// onClass() returns the Class objects
172 		assertList(a.onClass(), TC1.class, TC2.class);
173 
174 		// on() should be empty when only onClass() is used
175 		assertList(a.on());
176 	}
177 
178 	@Test
179 	void b02_onVsOnClass_onOnly() {
180 		var a = T.create().on("com.example.Class1", "com.example.Class2").build();
181 
182 		// on() returns the string targets
183 		assertList(a.on(), "com.example.Class1", "com.example.Class2");
184 
185 		// onClass() should be empty when only on() is used
186 		assertList(a.onClass());
187 	}
188 
189 	@Test
190 	void b03_onVsOnClass_both() {
191 		var a = T.create().on("com.example.Class1").onClass(TC1.class).build();
192 
193 		// Both should be independent
194 		assertList(a.on(), "com.example.Class1");
195 		assertList(a.onClass(), TC1.class);
196 	}
197 
198 	@Test
199 	void b04_onVsOnClass_onWithClassConversion() {
200 		var a = T.create().on(TC1.class, TC2.class).build();
201 
202 		// on(Class...) converts to strings
203 		assertList(a.on(), CNAME + "$TC1", CNAME + "$TC2");
204 
205 		// onClass() should be empty since we used on(Class...) not onClass(Class...)
206 		assertList(a.onClass());
207 	}
208 
209 	//------------------------------------------------------------------------------------------------------------------
210 	// ClassInfo tests
211 	//------------------------------------------------------------------------------------------------------------------
212 
213 	@Test
214 	void c01_classInfo_onClassInfo() {
215 		var ci1 = ClassInfo.of(TC1.class);
216 		var ci2 = ClassInfo.of(TC2.class);
217 
218 		var a = T.create().onClass(ci1, ci2).build();
219 
220 		assertList(a.onClass(), TC1.class, TC2.class);
221 	}
222 
223 	@Test
224 	void c02_classInfo_onClassInfo_mixed() {
225 		var ci = ClassInfo.of(TC1.class);
226 
227 		var a = T.create().onClass(ci).onClass(TC2.class).build();
228 
229 		assertList(a.onClass(), TC1.class, TC2.class);
230 	}
231 
232 	//------------------------------------------------------------------------------------------------------------------
233 	// Equality and hashcode tests
234 	//------------------------------------------------------------------------------------------------------------------
235 
236 	@Test
237 	void d01_equality_sameOnClass() {
238 		var a1 = T.create().onClass(TC1.class, TC2.class).value("test").build();
239 
240 		var a2 = T.create().onClass(TC1.class, TC2.class).value("test").build();
241 
242 		assertEquals(a1, a2);
243 		assertEquals(a1.hashCode(), a2.hashCode());
244 	}
245 
246 	@Test
247 	void d02_equality_differentOnClass() {
248 		var a1 = T.create().onClass(TC1.class).build();
249 
250 		var a2 = T.create().onClass(TC2.class).build();
251 
252 		assertNotEquals(a1, a2);
253 	}
254 
255 	@Test
256 	void d03_equality_differentOrder() {
257 		var a1 = T.create().onClass(TC1.class, TC2.class).build();
258 
259 		var a2 = T.create().onClass(TC2.class, TC1.class).build();
260 
261 		assertNotEquals(a1, a2);
262 	}
263 
264 	@Test
265 	void d04_equality_withDeclaredAnnotation() {
266 		@TA(onClass = { TC1.class, TC2.class }, value = "test")
267 		class TestClass {}
268 
269 		var declared = TestClass.class.getAnnotation(TA.class);
270 		var programmatic = T.create().onClass(TC1.class, TC2.class).value("test").build();
271 
272 		assertEquals(declared, programmatic);
273 		assertEquals(declared.hashCode(), programmatic.hashCode());
274 	}
275 
276 	@Test
277 	void d05_hashCode_consistency() {
278 		var a = T.create().onClass(TC1.class).value("test").build();
279 
280 		var hash1 = a.hashCode();
281 		var hash2 = a.hashCode();
282 		assertEquals(hash1, hash2, "hashCode should be consistent");
283 	}
284 
285 	//------------------------------------------------------------------------------------------------------------------
286 	// toMap() tests
287 	//------------------------------------------------------------------------------------------------------------------
288 
289 	@Test
290 	void e01_toMap_withOnClass() {
291 		var a = T.create().onClass(TC1.class, TC2.class).value("test").build();
292 		var map = ((T)a).propertyMap();
293 
294 		assertBean(map, "value,on,onClass", "test,[],[TC1,TC2]");
295 	}
296 
297 	@Test
298 	void e02_toMap_withBoth() {
299 		var a = T.create().on("com.example.Class1").onClass(TC1.class).value("test").build();
300 		var map = ((T)a).propertyMap();
301 
302 		assertBean(map, "value,on,onClass", "test,[com.example.Class1],[TC1]");
303 	}
304 
305 	@Test
306 	void e03_toMap_empty() {
307 		var a = T.create().build();
308 		var map = ((T)a).propertyMap();
309 
310 		assertBean(map, "value,on,onClass", ",[],[]");
311 	}
312 
313 	//------------------------------------------------------------------------------------------------------------------
314 	// toString() tests
315 	//------------------------------------------------------------------------------------------------------------------
316 
317 	@Test
318 	void f01_toString_notNull() {
319 		var a = T.create().build();
320 		var str = a.toString();
321 		assertNotNull(str);
322 		assertFalse(str.isEmpty());
323 	}
324 
325 	@Test
326 	void f02_toString_containsValues() {
327 		var a = T.create().onClass(TC1.class).value("test").build();
328 
329 		assertContainsAll(a.toString(), "test", "onClass");
330 	}
331 
332 	//------------------------------------------------------------------------------------------------------------------
333 	// Builder pattern tests
334 	//------------------------------------------------------------------------------------------------------------------
335 
336 	@Test
337 	void g01_builder_fluentApi() {
338 		var a = T.create().onClass(TC1.class).value("test").onClass(TC2.class).build();
339 
340 		assertList(a.onClass(), TC1.class, TC2.class);
341 		assertEquals("test", a.value());
342 	}
343 
344 	@Test
345 	void g02_builder_multipleBuilds() {
346 		var builder = T.create().onClass(TC1.class).value("test");
347 
348 		var a1 = builder.build();
349 		var a2 = builder.build();
350 
351 		// Different instances but equal
352 		assertNotSame(a1, a2);
353 		assertEquals(a1, a2);
354 	}
355 
356 	@Test
357 	void g03_builder_chaining() {
358 		var a = T.create().onClass(TC1.class).on("com.example.Class1").value("test").onClass(TC2.class).build();
359 
360 		assertList(a.onClass(), TC1.class, TC2.class);
361 		assertList(a.on(), "com.example.Class1");
362 		assertEquals("test", a.value());
363 	}
364 
365 	//------------------------------------------------------------------------------------------------------------------
366 	// Edge cases
367 	//------------------------------------------------------------------------------------------------------------------
368 
369 	@Test
370 	void h01_edgeCase_emptyOnClass() {
371 		var a = T.create().onClass(new Class<?>[0]).build();
372 
373 		assertList(a.onClass());
374 	}
375 
376 	@Test
377 	void h02_edgeCase_arrayEquality() {
378 		var a1 = T.create().onClass(TC1.class, TC2.class, TC3.class).build();
379 
380 		var a2 = T.create().onClass(new Class<?>[] { TC1.class, TC2.class, TC3.class }).build();
381 
382 		assertEquals(a1, a2);
383 	}
384 
385 	@Test
386 	void h03_edgeCase_equality_differentType() {
387 		var a = T.create().build();
388 		var other = new Object();
389 
390 		assertNotEquals(a, other);
391 	}
392 
393 	@Test
394 	void h04_edgeCase_equality_null() {
395 		var a = T.create().build();
396 		assertNotEquals(a, null);
397 	}
398 
399 	//------------------------------------------------------------------------------------------------------------------
400 	// Null validation tests
401 	//------------------------------------------------------------------------------------------------------------------
402 
403 	@Test
404 	void i01_null_classInArray() {
405 		assertThrows(IllegalArgumentException.class, () -> T.create().onClass((Class<?>)null).build());
406 	}
407 
408 	@Test
409 	void i02_null_classInfoInArray() {
410 		assertThrows(IllegalArgumentException.class, () -> T.create().onClass((ClassInfo)null).build());
411 	}
412 
413 	@Test
414 	void i03_null_builder() {
415 		assertThrows(IllegalArgumentException.class, () -> new T(null));
416 	}
417 }