1 package org.djunits;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.fail;
5
6 import java.io.Serializable;
7 import java.lang.reflect.Method;
8 import java.lang.reflect.Modifier;
9 import java.util.Collection;
10
11 import org.junit.jupiter.api.Test;
12
13 /**
14 * Verify that all classes have a toString method (unless the class in non-instantiable, or an enum, or abstract. <br>
15 * Verify that no class overrides equals without overriding hashCode. <br>
16 * Verify that classes that can be instantiated are Serializable for those classes that this makes sense.
17 * <p>
18 * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19 * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
20 * </p>
21 * @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 13, 2016 <br>
22 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
25 */
26 public class VerifyRequiredMethods
27 {
28 /**
29 * Name of the top level package.
30 */
31 private static String packageName = "org.djunits";
32
33 /**
34 * Check that all classes have a toString method.
35 */
36 // FIXME: @Test
37 public final void toStringTest()
38 {
39 int failures = 0;
40 Collection<Class<?>> classList = ClassList.classList(packageName, true);
41 for (Class<?> c : classList)
42 {
43 if (Exception.class.isAssignableFrom(c))
44 {
45 continue;
46 }
47 if (ClassList.isAnonymousInnerClass(c))
48 {
49 continue;
50 }
51 Method toStringMethod = null;
52 boolean isAbstract = false;
53 for (String modifierString : Modifier.toString(c.getModifiers()).split(" "))
54 {
55 if (modifierString.equals("abstract"))
56 {
57 isAbstract = true;
58 break;
59 }
60 }
61 for (Method m : c.getDeclaredMethods())
62 {
63 if (m.getName().equals("toString") && m.getParameterCount() == 0)
64 {
65 toStringMethod = m;
66 }
67 }
68 if (null == toStringMethod)
69 {
70 // Get the nearest toString method from the parent tree
71 try
72 {
73 toStringMethod = c.getMethod("toString");
74 }
75 catch (NoSuchMethodException | SecurityException exception)
76 {
77 exception.printStackTrace();
78 fail("Cannot happen: getMethod(\"toString\") should never fail - there is one in Object");
79 }
80 boolean isFinal = false;
81 for (String modifierString : Modifier.toString(toStringMethod.getModifiers()).split(" "))
82 {
83 if ("final".equals(modifierString))
84 {
85 isFinal = true;
86 break;
87 }
88 }
89 if (isFinal)
90 {
91 // system.out.println("Class " + c.getName() + " can not override the toString method because a super "
92 // + "class implements a final toString method");
93 }
94 else if (!ClassList.hasNonStaticFields(c))
95 {
96 // system.out.println("Class " + c.getName()
97 // + " does not have to override the toString method because it does not have non-static fields");
98 }
99 else if (isAbstract)
100 {
101 // system.out.println("Class " + c.getName() + " does not have to override the toString method because "
102 // + "it is an abstract class");
103 }
104 else if (c.isEnum())
105 {
106 // system.out.println(
107 // "Class " + c.getName() + " does not have to override toString because this class " + "is an enum");
108 }
109 else if (toStringMethod.getDeclaringClass().getPackage().getName().startsWith(packageName))
110 {
111 // system.out.println("Class " + c.getName()
112 // + " does not have to override the toString method because a super class with the package "
113 // + toStringMethod.getName() + " overrides toString (though not in a final way)");
114 }
115 else
116 {
117 System.err.println("Class " + c.getName() + " does not (but should) override toString");
118 failures++;
119 }
120 }
121 }
122 assertEquals(0, failures, "Failures to implement toString");
123 }
124
125 /**
126 * Check that all classes implement the Serializable interface.
127 */
128 // FIXME: @Test
129 @SuppressWarnings({"checkstyle:methodlength", "checkstyle:emptyblock"})
130 public final void serializableTest()
131 {
132 int failures = 0;
133 Collection<Class<?>> classList = ClassList.classList(packageName, true);
134 for (Class<?> c : classList)
135 {
136 if (Serializable.class.isAssignableFrom(c))
137 {
138 if (c.isEnum())
139 {
140 // System.out.println("Class " + c.getName() + " is an enum and (by inheritance) implements Serializable");
141 }
142 else if (!ClassList.hasNonStaticFields(c))
143 {
144 System.err.println("Class " + c.getName()
145 + " does not contain non-static fields and should NOT implement Serializable");
146 failures++;
147 }
148 else if (Thread.class.isAssignableFrom(c))
149 {
150 System.err.println("Class " + c.getName() + " is a thread and should NOT implement Serializable");
151 failures++;
152 }
153 else if (ClassList.isAnonymousInnerClass(c))
154 {
155 System.err.println(
156 "Class " + c.getName() + " is an anonymous inner class and should NOT implement Serializable");
157 failures++;
158 }
159 else if (Exception.class.isAssignableFrom(c))
160 {
161 // system.out.println("Class " + c.getName() + " is an Exception and (correctly) implements Serializable");
162 }
163 else
164 {
165 // System.out.println("Class " + c.getName() + " should (and does) implement Serializable");
166 }
167 }
168 else
169 {
170 if (c.isEnum())
171 {
172 System.err
173 .println("Class " + c.getName() + " is an enum and should (by inheritence) implement Serializable");
174 failures++;
175 }
176 else if (!ClassList.hasNonStaticFields(c))
177 {
178 // System.out.println("Class " + c.getName()
179 // + " does not contain non-static fields and (correctly does not implement Serializable");
180 }
181 else if (Thread.class.isAssignableFrom(c))
182 {
183 // System.out.println("Class " + c.getName() +
184 // " is a thread and (correctly) does not implement Serializable");
185 }
186 else if (ClassList.isAnonymousInnerClass(c))
187 {
188 // System.out.println("Class " + c.getName()
189 // + " is an anonymous inner class and (correctly) does not implement Serializable");
190 }
191 else if (Exception.class.isAssignableFrom(c))
192 {
193 System.err.println(
194 "Class " + c.getName() + " is an Exception and should (but does NOT) implement Serializable");
195 failures++;
196 }
197 else
198 {
199 System.err.println("Class " + c.getName() + " should (but does NOT) implement Serializable");
200 failures++;
201 }
202 }
203 }
204 assertEquals(0, failures, "Failures to implement serializable");
205 }
206
207 /**
208 * Check that all classes that implement equals also implement hashCode.
209 */
210 @Test
211 @SuppressWarnings("checkstyle:emptyblock")
212 public final void equalsAndHashCodeTest()
213 {
214 int failures = 0;
215 Collection<Class<?>> classList = ClassList.classList(packageName, true);
216 for (Class<?> c : classList)
217 {
218 if (Exception.class.isAssignableFrom(c))
219 {
220 continue;
221 }
222 Method equalsMethod = null;
223 Method hashCodeMethod = null;
224 for (Method m : c.getDeclaredMethods())
225 {
226 if (m.getName().equals("equals"))
227 {
228 equalsMethod = m;
229 }
230 else if (m.getName().equals("hashCode"))
231 {
232 hashCodeMethod = m;
233 }
234 }
235 if (null == equalsMethod)
236 {
237 if (null == hashCodeMethod)
238 {
239 // System.out.println("Class " + c.getName() + " implements neither equals nor hashCode");
240 }
241 else
242 {
243 System.err.println("Class " + c.getName() + " implements hashCode, but not equals");
244 failures++;
245 }
246 }
247 else if (null == hashCodeMethod)
248 {
249 System.err.println("Class " + c.getName() + " implements equals but NOT hashCode");
250 failures++;
251 }
252 else
253 {
254 // System.out.println("Class " + c.getName() + " implements equals and hashCode (good)");
255 }
256 }
257 assertEquals(0, failures, "Failures to implement both hashCode and equals");
258 }
259
260 }