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  import java.util.*;
24  
25  import org.apache.juneau.*;
26  import org.junit.jupiter.api.*;
27  
28  class AnnotationObject_Test extends TestBase {
29  
30  	//------------------------------------------------------------------------------------------------------------------
31  	// Test annotation for testing purposes
32  	//------------------------------------------------------------------------------------------------------------------
33  
34  	@Target({ ElementType.TYPE, ElementType.METHOD })
35  	@Retention(RetentionPolicy.RUNTIME)
36  	public @interface TA {
37  		String value() default "";
38  
39  		int number() default 0;
40  
41  		boolean flag() default false;
42  
43  		String[] array() default {};
44  	}
45  
46  	/**
47  	 * Implementation of TA using AnnotationObject
48  	 */
49  	public static class T extends AnnotationObject implements TA {
50  
51  		private final String value;
52  		private final int number;
53  		private final boolean flag;
54  		private final String[] array;
55  
56  		public static class Builder extends AnnotationObject.Builder {
57  			String value = "";
58  			int number = 0;
59  			boolean flag = false;
60  			String[] array = {};
61  
62  			public Builder() {
63  				super(TA.class);
64  			}
65  
66  			public Builder value(String _value) {
67  				value = _value;
68  				return this;
69  			}
70  
71  			public Builder number(int value) {
72  				number = value;
73  				return this;
74  			}
75  
76  			public Builder flag(boolean value) {
77  				flag = value;
78  				return this;
79  			}
80  
81  			public Builder array(String...value) {
82  				array = value;
83  				return this;
84  			}
85  
86  			public TA build() {
87  				return new T(this);
88  			}
89  		}
90  
91  		public static Builder create() {
92  			return new Builder();
93  		}
94  
95  		public T(Builder b) {
96  			super(b);
97  			value = b.value;
98  			number = b.number;
99  			flag = b.flag;
100 			array = Arrays.copyOf(b.array, b.array.length);
101 		}
102 
103 		@Override
104 		public String value() {
105 			return value;
106 		}
107 
108 		@Override
109 		public int number() {
110 			return number;
111 		}
112 
113 		@Override
114 		public boolean flag() {
115 			return flag;
116 		}
117 
118 		@Override
119 		public String[] array() {
120 			return array;
121 		}
122 	}
123 
124 	//------------------------------------------------------------------------------------------------------------------
125 	// Basic tests
126 	//------------------------------------------------------------------------------------------------------------------
127 
128 	@Test
129 	void a01_basic_defaultValues() {
130 		var a = T.create().build();
131 		assertBean(a, "value,number,flag,array", ",0,false,[]");
132 	}
133 
134 	@Test
135 	void a02_basic_customValues() {
136 		var a = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
137 		assertBean(a, "value,number,flag,array", "a,1,true,[b1,b2]");
138 	}
139 
140 	@Test
141 	void a03_basic_annotationType() {
142 		var a = T.create().build();
143 		assertEquals(TA.class, a.annotationType());
144 	}
145 
146 	//------------------------------------------------------------------------------------------------------------------
147 	// Equality and hashcode tests
148 	//------------------------------------------------------------------------------------------------------------------
149 
150 	@Test
151 	void b01_equality_identical() {
152 		var a1 = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
153 
154 		var a2 = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
155 
156 		assertEquals(a1, a2);
157 		assertEquals(a1.hashCode(), a2.hashCode());
158 	}
159 
160 	@Test
161 	void b02_equality_different() {
162 		var a1 = T.create().value("a1").build();
163 
164 		var a2 = T.create().value("a2").build();
165 
166 		assertNotEquals(a1, a2);
167 	}
168 
169 	@Test
170 	void b03_equality_withDeclaredAnnotation() {
171 		@TA(value = "a", number = 1, flag = true, array = { "b1", "b2" })
172 		class B {}
173 
174 		var declared = B.class.getAnnotation(TA.class);
175 		var programmatic = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
176 
177 		assertEquals(declared, programmatic);
178 		assertEquals(declared.hashCode(), programmatic.hashCode());
179 	}
180 
181 	@Test
182 	void b04_hashCode_consistency() {
183 		var a = T.create().value("a").number(1).build();
184 
185 		var hash1 = a.hashCode();
186 		var hash2 = a.hashCode();
187 		assertEquals(hash1, hash2, "hashCode should be consistent");
188 	}
189 
190 	@Test
191 	void b05_hashCode_notNegativeOne() {
192 		var a = T.create().build();
193 		assertNotEquals(-1, a.hashCode());
194 	}
195 
196 	//------------------------------------------------------------------------------------------------------------------
197 	// toMap() tests
198 	//------------------------------------------------------------------------------------------------------------------
199 
200 	@Test
201 	void c01_toMap_defaultValues() {
202 		var a = T.create().build();
203 		assertBean(((T)a).propertyMap(), "value,number,flag,array", ",0,false,[]");
204 	}
205 
206 	@Test
207 	void c02_toMap_customValues() {
208 		var a = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
209 
210 		assertBean(((T)a).propertyMap(), "value,number,flag,array", "a,1,true,[b1,b2]");
211 	}
212 
213 	@Test
214 	void c03_toMap_keySorted() {
215 		var a = T.create().build();
216 		var map = ((T)a).propertyMap();
217 
218 		// Map should be ordered by key name
219 		assertList(map.keySet(), "array", "flag", "number", "value");
220 	}
221 
222 	//------------------------------------------------------------------------------------------------------------------
223 	// toString() tests
224 	//------------------------------------------------------------------------------------------------------------------
225 
226 	@Test
227 	void d01_toString_notNull() {
228 		var a = T.create().build();
229 		var str = a.toString();
230 		assertNotNull(str);
231 		assertFalse(str.isEmpty());
232 	}
233 
234 	@Test
235 	void d02_toString_containsValues() {
236 		var a = T.create().value("a").number(1).build();
237 
238 		assertContainsAll(a.toString(), "a", "1");
239 	}
240 
241 	//------------------------------------------------------------------------------------------------------------------
242 	// Constructor validation tests
243 	//------------------------------------------------------------------------------------------------------------------
244 
245 	@Test
246 	void e01_constructor_nullAnnotationType() {
247 		assertThrows(IllegalArgumentException.class, () -> new AnnotationObject.Builder(null));
248 	}
249 
250 	//------------------------------------------------------------------------------------------------------------------
251 	// Builder pattern tests
252 	//------------------------------------------------------------------------------------------------------------------
253 
254 	@Test
255 	void f01_builder_fluentApi() {
256 		var a = T.create().value("a").number(1).flag(true).array("b1", "b2").build();
257 
258 		assertBean(a, "value,number,flag,array", "a,1,true,[b1,b2]");
259 	}
260 
261 	@Test
262 	void f02_builder_multipleBuilds() {
263 		var builder = T.create().value("a").number(1);
264 
265 		var a1 = builder.build();
266 		var a2 = builder.build();
267 
268 		// Different instances but equal
269 		assertNotSame(a1, a2);
270 		assertEquals(a1, a2);
271 	}
272 
273 	@Test
274 	void f03_builder_getAnnotationType() {
275 		var builder = T.create();
276 		assertEquals(TA.class, builder.getAnnotationType());
277 	}
278 
279 	//------------------------------------------------------------------------------------------------------------------
280 	// Edge cases
281 	//------------------------------------------------------------------------------------------------------------------
282 
283 	@Test
284 	void g01_arrayEquality_emptyVsNull() {
285 		// Empty array and null should be handled consistently
286 		var a1 = T.create().array().build();
287 
288 		var a2 = T.create().array(new String[0]).build();
289 
290 		assertEquals(a1, a2);
291 	}
292 
293 	@Test
294 	void g02_arrayEquality_deepEquals() {
295 		// Arrays with same content should be equal
296 		var a1 = T.create().array("a", "b", "c").build();
297 
298 		var a2 = T.create().array(new String[] { "a", "b", "c" }).build();
299 
300 		assertEquals(a1, a2);
301 	}
302 
303 	@Test
304 	void g03_arrayEquality_differentOrder() {
305 		// Arrays with different order should not be equal
306 		var a1 = T.create().array("a", "b", "c").build();
307 
308 		var a2 = T.create().array("c", "b", "a").build();
309 
310 		assertNotEquals(a1, a2);
311 	}
312 
313 	@Test
314 	void g04_equality_differentType() {
315 		var a = T.create().build();
316 		var other = new Object();
317 
318 		assertNotEquals(a, other);
319 	}
320 
321 	@Test
322 	void g05_equality_null() {
323 		var a = T.create().build();
324 		assertNotEquals(a, null);
325 	}
326 
327 	//------------------------------------------------------------------------------------------------------------------
328 	// Null validation tests
329 	//------------------------------------------------------------------------------------------------------------------
330 
331 	@Test
332 	void h01_nullBuilder_throwsException() {
333 		assertThrows(IllegalArgumentException.class, () -> new T(null));
334 	}
335 }