View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.archunit;
16  
17  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
18  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.constructors;
19  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
20  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
21  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
22  
23  import java.lang.reflect.Executable;
24  import java.util.Objects;
25  import java.util.function.Supplier;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.htmlunit.corejs.javascript.Scriptable;
29  import org.htmlunit.javascript.configuration.JsxClass;
30  import org.htmlunit.javascript.configuration.JsxClasses;
31  import org.htmlunit.javascript.configuration.JsxConstant;
32  import org.htmlunit.javascript.configuration.JsxConstructor;
33  import org.htmlunit.javascript.configuration.JsxFunction;
34  import org.htmlunit.javascript.configuration.JsxGetter;
35  import org.htmlunit.javascript.configuration.JsxSetter;
36  
37  import com.tngtech.archunit.base.DescribedPredicate;
38  import com.tngtech.archunit.core.domain.JavaClass;
39  import com.tngtech.archunit.core.domain.JavaMethod;
40  import com.tngtech.archunit.core.domain.JavaModifier;
41  import com.tngtech.archunit.core.importer.ImportOption;
42  import com.tngtech.archunit.junit.AnalyzeClasses;
43  import com.tngtech.archunit.junit.ArchTest;
44  import com.tngtech.archunit.lang.ArchCondition;
45  import com.tngtech.archunit.lang.ArchRule;
46  import com.tngtech.archunit.lang.ConditionEvents;
47  import com.tngtech.archunit.lang.SimpleConditionEvent;
48  
49  /**
50   * Architecture tests.
51   *
52   * @author Ronald Brill
53   */
54  @AnalyzeClasses(packages = "org.htmlunit", importOptions = ImportOption.DoNotIncludeTests.class)
55  public class ArchitectureTest {
56  
57      /**
58       * Utility classes should be placed in 'org.htmlunit.util'.
59       */
60      @ArchTest
61      public static final ArchRule utilsPackageRule = classes()
62          .that().haveNameMatching(".*Util.?")
63          .and().doNotHaveFullyQualifiedName("org.htmlunit.cssparser.util.ParserUtils")
64          .and().doNotHaveFullyQualifiedName("org.htmlunit.http.HttpUtils")
65          .and().doNotHaveFullyQualifiedName("org.htmlunit.util.brotli.Utils")
66  
67          .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.font.AwtFontUtil")
68          .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.font.FontUtil")
69          .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.font.NoOpFontUtil")
70  
71          .and().doNotHaveFullyQualifiedName("org.htmlunit.csp.Utils")
72          .and().doNotHaveFullyQualifiedName("org.htmlunit.cyberneko.util.StringUtils")
73  
74          .and().resideOutsideOfPackage("org.htmlunit.jetty.util..")
75          .and().doNotHaveFullyQualifiedName("org.htmlunit.jetty.websocket.api.util.QuoteUtil")
76          .and().doNotHaveFullyQualifiedName("org.htmlunit.jetty.websocket.common.util.ReflectUtils")
77          .and().doNotHaveFullyQualifiedName("org.htmlunit.jetty.websocket.common.util.TextUtil")
78  
79          .and().resideOutsideOfPackage("org.htmlunit.corejs..")
80  
81          .should().resideInAPackage("org.htmlunit.util");
82  
83      /**
84       * Do not use awt if not really needed (because not available on android).
85       */
86      @ArchTest
87      public static final ArchRule awtPackageRule = noClasses()
88          .that()
89              .resideOutsideOfPackage("org.htmlunit.platform..")
90              .and().resideOutsideOfPackage("org.htmlunit.corejs.javascript.tools..")
91              .and().resideOutsideOfPackage("org.htmlunit.jetty..")
92          .should().dependOnClassesThat().resideInAnyPackage("java.awt..");
93  
94      /**
95       * Do not use org.apache.commons.lang3.StringUtils.isEmpty(CharSequence).
96       */
97      @ArchTest
98      public static final ArchRule apacheStringUtilsIsEmptyRule = noClasses()
99          .should().callMethod(org.apache.commons.lang3.StringUtils.class, "isEmpty", CharSequence.class);
100 
101     /**
102      * Do not use org.apache.commons.lang3.StringUtils.isBlank(CharSequence).
103      */
104     @ArchTest
105     public static final ArchRule apacheStringUtilsIsBlankRule = noClasses()
106         .should().callMethod(org.apache.commons.lang3.StringUtils.class, "isBlank", CharSequence.class);
107 
108     /**
109      * Do not use org.apache.commons.lang3.StringUtils.isNotBlank(CharSequence).
110      */
111     @ArchTest
112     public static final ArchRule apacheStringUtilsIsNotBlankRule = noClasses()
113         .should().callMethod(org.apache.commons.lang3.StringUtils.class, "isNotBlank", CharSequence.class);
114 
115     /**
116      * Do not use org.apache.commons.lang3.StringUtils.toRootLowerCase(String).
117      */
118     @ArchTest
119     public static final ArchRule apacheStringUtilstoRootLowerCaseRule = noClasses()
120         .should().callMethod(org.apache.commons.lang3.StringUtils.class, "toRootLowerCase", String.class);
121 
122     /**
123      * Do not use org.apache.commons.lang3.StringUtils.substringBefore(String, string).
124      */
125     @ArchTest
126     public static final ArchRule apacheStringUtilsSubstringBeforeRule = noClasses()
127         .should().callMethod(org.apache.commons.lang3.StringUtils.class, "substringBefore", String.class, String.class);
128 
129     /**
130      * Do not use org.apache.commons.lang3.StringUtils.isNotEmpty(String).
131      */
132     @ArchTest
133     public static final ArchRule apacheStringUtilsIsNotEmptyRule = noClasses()
134         .should().callMethod(org.apache.commons.lang3.math.NumberUtils.class, "toInt", String.class, Integer.class);
135 
136     /**
137      * Do not use org.apache.commons.lang3.Strings.startsWith(CharSequence, CharSequence).
138      */
139     @ArchTest
140     public static final ArchRule apacheStringsStartsWithRule = noClasses()
141         .should().callMethod(org.apache.commons.lang3.Strings.class, "startsWith", CharSequence.class, CharSequence.class);
142 
143     /**
144      * Do not use org.apache.commons.lang3.Strings.endsWith(CharSequence, CharSequence).
145      */
146     @ArchTest
147     public static final ArchRule apacheStringsEndsWithRule = noClasses()
148         .should().callMethod(org.apache.commons.lang3.Strings.class, "endsWith", CharSequence.class, CharSequence.class);
149 
150     /**
151      * Do not use org.apache.commons.lang3.Strings.contains(CharSequence, CharSequence).
152      */
153     @ArchTest
154     public static final ArchRule apacheStringsContainsRule = noClasses()
155         .should().callMethod(org.apache.commons.lang3.Strings.class, "contains", CharSequence.class, CharSequence.class);
156 
157 
158     /**
159      * Do not use org.apache.commons.lang3.math.NumberUtils.
160      */
161     @ArchTest
162     public static final ArchRule apacheNumberUtilsRule = noClasses()
163         .should().dependOnClassesThat().haveFullyQualifiedName("org.apache.commons.lang3.math.NumberUtils");
164 
165     /**
166      * The jetty websocket stuff is only used by one class.
167      */
168     @ArchTest
169     public static final ArchRule webSocketPackageRule = noClasses()
170             .that()
171                 .resideOutsideOfPackage("org.htmlunit.jetty..")
172                 .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter")
173                 .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter$JettyWebSocketAdapterFactory")
174                 .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter$JettyWebSocketAdapterImpl")
175             .should()
176                 .dependOnClassesThat().resideInAnyPackage("org.htmlunit.jetty..");
177 
178     /**
179      * JsxClasses are always in the javascript package.
180      */
181     @ArchTest
182     public static final ArchRule jsxClassAnnotationPackages = classes()
183             .that().areAnnotatedWith(JsxClass.class)
184             .should().resideInAPackage("..javascript..");
185 
186     /**
187      * Every JsxConstant should be public static final.
188      *
189      * AbstractJavaScriptConfiguration.process(ClassConfiguration, String, SupportedBrowser)
190      * stores the value.
191      */
192     @ArchTest
193     public static final ArchRule jsxConstant = fields()
194             .that().areAnnotatedWith(JsxConstant.class)
195             .should().haveModifier(JavaModifier.PUBLIC)
196             .andShould().haveModifier(JavaModifier.STATIC)
197             .andShould().haveModifier(JavaModifier.FINAL);
198 
199     /**
200      * Every JsxConstant should be a string, int, or long.
201      */
202     @ArchTest
203     public static final ArchRule jsxConstantType = fields()
204             .that().areAnnotatedWith(JsxConstant.class)
205             .should().haveRawType(String.class)
206             .orShould().haveRawType("int")
207             .orShould().haveRawType("long");
208 
209     /**
210      * JsxGetter/Setter/Functions are always in the javascript package.
211      */
212     @ArchTest
213     public static final ArchRule jsxAnnotationPackages = methods()
214             .that().areAnnotatedWith(JsxGetter.class)
215                     .or().areAnnotatedWith(JsxSetter.class)
216                     .or().areAnnotatedWith(JsxFunction.class)
217                     .or().areAnnotatedWith(JsxConstructor.class)
218                     .or().areAnnotatedWith(JsxConstant.class)
219             .should().beDeclaredInClassesThat().resideInAPackage("..javascript..");
220 
221     /**
222      * JsxGetter/Setter/Functions only valid in classes annotated as JsxClass.
223      */
224     @ArchTest
225     public static final ArchRule jsxAnnotationJsxClass = methods()
226             .that().areAnnotatedWith(JsxGetter.class)
227                     .or().areAnnotatedWith(JsxSetter.class)
228                     .or().areAnnotatedWith(JsxFunction.class)
229                     .or().areAnnotatedWith(JsxConstructor.class)
230                     .or().areAnnotatedWith(JsxConstant.class)
231             .should().beDeclaredInClassesThat().areAnnotatedWith(JsxClass.class)
232             .orShould().beDeclaredInClassesThat().areAnnotatedWith(JsxClasses.class);
233 
234     private static final DescribedPredicate<? super JavaClass> isAssignableToScriptable =
235             new DescribedPredicate<JavaClass>("@is not assignable to Scriptable") {
236                 @Override
237                 public boolean test(final JavaClass javaClass) {
238                     // we have to build a more complex implemenation because
239                     // javaClass.isAssignableTo(Scriptable.class);
240                     // checks also all superclasses
241                     // Therefore we have to switch back to the real java class.
242                     try {
243                         if (javaClass.isPrimitive()) {
244                             return false;
245                         }
246 
247                         return Scriptable.class.isAssignableFrom(Class.forName(javaClass.getFullName()));
248                     }
249                     catch (final ClassNotFoundException e) {
250                         throw new RuntimeException(e.getMessage(), e);
251                     }
252                 }
253             };
254 
255     /**
256      * JsxGetter should only return Scriptable's.
257      */
258     @ArchTest
259     public static final ArchRule jsxGetterReturnType = methods()
260             .that()
261                 .areAnnotatedWith(JsxGetter.class)
262                 .and().doNotHaveFullName("org.htmlunit.javascript.host.History.getState()")
263                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Navigator.getDoNotTrack()")
264                 .and().doNotHaveFullName("org.htmlunit.javascript.host.URL.getOrigin()")
265                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getClientInformation()")
266                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getControllers()")
267                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getEvent()")
268                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getFrames_js()")
269                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getLength()")
270                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getOpener()")
271                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getParent()")
272                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getSelf()")
273                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.getTop()")
274                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.AbstractRange.getEndContainer()")
275                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.AbstractRange.getStartContainer()")
276                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.CharacterData.getData()")
277                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.DOMException.getCode()")
278                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.DOMException.getFilename()")
279                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.DOMException.getLineNumber()")
280                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.DOMException.getMessage()")
281                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.Document.getActiveElement()")
282                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.Document.getDefaultView()")
283                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.Document.getHead()")
284                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.Node.getParentNode()")
285                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.NodeIterator.getFilter()")
286                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.Range.getCommonAncestorContainer()")
287                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.TreeWalker.getFilter()")
288                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.BeforeUnloadEvent.getReturnValue()")
289                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.CustomEvent.getDetail()")
290                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.Event.getReturnValue()")
291                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.Event.getSrcElement()")
292                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.Event.getTarget()")
293                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.InputEvent.getData()")
294                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.InputEvent.getInputType()")
295                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.MessageEvent.getData()")
296                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.MessageEvent.getPorts()")
297                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.PopStateEvent.getState()")
298                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.ProgressEvent.getLoaded()")
299                 .and().doNotHaveFullName("org.htmlunit.javascript.host.event.TextEvent.getData()")
300                 .and().doNotHaveFullName("org.htmlunit.javascript.host.file.FileReader.getResult()")
301                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLButtonElement.getValue()")
302                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLDataElement.getValue()")
303                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLElement.getHidden()")
304                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLInputElement.getValue()")
305                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLMeterElement.getValue()")
306                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLOptionElement.getValue()")
307                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLParamElement.getValue()")
308                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLProgressElement.getValue()")
309                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLSelectElement.getValue()")
310                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLTextAreaElement.getValue()")
311                 .and().doNotHaveFullName("org.htmlunit.javascript.host.worker.DedicatedWorkerGlobalScope.getSelf()")
312                 .and().doNotHaveFullName("org.htmlunit.javascript.host.xml.XMLHttpRequest.getResponse()")
313                 .and().doNotHaveFullName("org.htmlunit.javascript.host.xml.XMLHttpRequest.getResponseXML()")
314 
315             .should().haveRawReturnType(String.class)
316             .orShould().haveRawReturnType("int")
317             .orShould().haveRawReturnType(Integer.class)
318             .orShould().haveRawReturnType("long")
319             .orShould().haveRawReturnType("double")
320             .orShould().haveRawReturnType(Double.class)
321             .orShould().haveRawReturnType("boolean")
322             .orShould().haveRawReturnType(isAssignableToScriptable);
323 
324     /**
325      * JsxSetter should only return void.
326      */
327     @ArchTest
328     public static final ArchRule jsxSetterReturnType = methods()
329             .that()
330                 .areAnnotatedWith(JsxSetter.class)
331             .should().haveRawReturnType("void");
332 
333     /**
334      * JsxFunctions should only return Scriptable's.
335      */
336     @ArchTest
337     public static final ArchRule jsxFunctionReturnType = methods()
338             .that()
339                 .areAnnotatedWith(JsxFunction.class)
340                 .and().doNotHaveFullName("org.htmlunit.javascript.host.External.isSearchProviderInstalled()")
341                 .and().doNotHaveFullName("org.htmlunit.javascript.host.SimpleArray.item(int)")
342                 .and().doNotHaveFullName("org.htmlunit.javascript.host.SimpleArray.namedItem(java.lang.String)")
343                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Storage.getItem(java.lang.String)")
344                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.setInterval(org.htmlunit.corejs.javascript.Context, org.htmlunit.corejs.javascript.Scriptable, org.htmlunit.corejs.javascript.Scriptable, [Ljava.lang.Object;, org.htmlunit.corejs.javascript.Function)")
345                 .and().doNotHaveFullName("org.htmlunit.javascript.host.Window.setTimeout(org.htmlunit.corejs.javascript.Context, org.htmlunit.corejs.javascript.Scriptable, org.htmlunit.corejs.javascript.Scriptable, [Ljava.lang.Object;, org.htmlunit.corejs.javascript.Function)")
346                 .and().doNotHaveFullName("org.htmlunit.javascript.host.css.CSSRuleList.item(int)")
347                 .and().doNotHaveFullName("org.htmlunit.javascript.host.css.StyleSheetList.item(int)")
348                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.DOMMatrixReadOnly.toJSON()")
349                 .and().doNotHaveFullName("org.htmlunit.javascript.host.dom.NodeList.item(java.lang.Object)")
350                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLAllCollection.item(java.lang.Object)")
351                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLCollection.item(java.lang.Object)")
352                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLOptionsCollection.item(int)")
353                 .and().doNotHaveFullName("org.htmlunit.javascript.host.html.HTMLSelectElement.item(int)")
354                 .and().doNotHaveFullName("org.htmlunit.javascript.host.intl.V8BreakIterator.resolvedOptions()")
355                 .and().doNotHaveFullName("org.htmlunit.javascript.host.performance.PerformanceNavigation.toJSON()")
356                 .and().doNotHaveFullName("org.htmlunit.javascript.host.worker.DedicatedWorkerGlobalScope.setInterval(org.htmlunit.corejs.javascript.Context, org.htmlunit.corejs.javascript.Scriptable, org.htmlunit.corejs.javascript.Scriptable, [Ljava.lang.Object;, org.htmlunit.corejs.javascript.Function)")
357                 .and().doNotHaveFullName("org.htmlunit.javascript.host.worker.DedicatedWorkerGlobalScope.setTimeout(org.htmlunit.corejs.javascript.Context, org.htmlunit.corejs.javascript.Scriptable, org.htmlunit.corejs.javascript.Scriptable, [Ljava.lang.Object;, org.htmlunit.corejs.javascript.Function)")
358                 .and().doNotHaveFullName("org.htmlunit.javascript.host.xml.XSLTProcessor.getParameter(java.lang.String, java.lang.String)")
359 
360             .should().haveRawReturnType(String.class)
361             .orShould().haveRawReturnType("int")
362             .orShould().haveRawReturnType("long")
363             .orShould().haveRawReturnType("double")
364             .orShould().haveRawReturnType("boolean")
365             .orShould().haveRawReturnType("void")
366             .orShould().haveRawReturnType(isAssignableToScriptable);
367 
368     /**
369      * JsxConstructor should not used for constructors.
370      */
371     @ArchTest
372     public static final ArchRule jsxAnnotationJsxConstructor = constructors()
373             .should().notBeAnnotatedWith(JsxConstructor.class);
374 
375     /**
376      * JsxConstructor should have name jsConstructor.
377      */
378     @ArchTest
379     public static final ArchRule jsxAnnotationJsxConstructorNaming = methods()
380             .that().areAnnotatedWith(JsxConstructor.class)
381             .should().haveName("jsConstructor");
382 
383     /**
384      * JsxGetter/Setter/Functions should not use a short as parameter.
385      */
386     @ArchTest
387     public static final ArchRule jsxAnnotationParameterType = methods()
388             .that().areAnnotatedWith(JsxGetter.class)
389                     .or().areAnnotatedWith(JsxSetter.class)
390                     .or().areAnnotatedWith(JsxFunction.class)
391             .should().notHaveRawParameterTypes("short")
392             .andShould().notHaveRawParameterTypes("float");
393 
394     /**
395      * Every JsxGetter has to be named get... or is....
396      */
397     @ArchTest
398     public static final ArchRule jsxGetterAnnotationStartsWithGet = methods()
399             .that().areAnnotatedWith(JsxGetter.class)
400             .should().haveNameStartingWith("get")
401             .orShould().haveNameStartingWith("is");
402 
403     private static final DescribedPredicate<JavaMethod> haveJsxGetterWithNonDefaultPropertyName =
404             new DescribedPredicate<JavaMethod>("@JsxGetter has a non default propertyName") {
405                 @Override
406                 public boolean test(final JavaMethod method) {
407                     return StringUtils.isNotEmpty(method.getAnnotationOfType(JsxGetter.class).propertyName());
408                 }
409             };
410 
411     /**
412      * Every JsxGetter with propertyName defined has to end in '_js'.
413      */
414     @ArchTest
415     public static final ArchRule jsxGetterAnnotationPostfix = methods()
416             .that().areAnnotatedWith(JsxGetter.class)
417             .and(haveJsxGetterWithNonDefaultPropertyName)
418             .should().haveNameEndingWith("_js");
419 
420     /**
421      * Every JsxGetter has to be named get... or is....
422      */
423     @ArchTest
424     public static final ArchRule jsxSetterAnnotationStartsWithSet = methods()
425             .that().areAnnotatedWith(JsxSetter.class)
426             .should().haveNameStartingWith("set");
427 
428     private static final DescribedPredicate<JavaMethod> haveJsxSetterWithNonDefaultPropertyName =
429             new DescribedPredicate<JavaMethod>("@JsxSetter has a non default propertyName") {
430                 @Override
431                 public boolean test(final JavaMethod method) {
432                     return StringUtils.isNotEmpty(method.getAnnotationOfType(JsxSetter.class).propertyName());
433                 }
434             };
435 
436     /**
437      * Every JsxSetter with propertyName defined has to end in '_js'.
438      */
439     @ArchTest
440     public static final ArchRule jsxSetterAnnotationPostfix = methods()
441             .that().areAnnotatedWith(JsxSetter.class)
442             .and(haveJsxSetterWithNonDefaultPropertyName)
443             .should().haveNameEndingWith("_js");
444 
445     private static final ArchCondition<JavaMethod> hasMatchingGetter =
446             new ArchCondition<JavaMethod>("every @JsxSetter needs a matching @JsxGetter") {
447                 @Override
448                 public void check(final JavaMethod method, final ConditionEvents events) {
449                     String getterName = "g" + method.getName().substring(1);
450                     if (method.getOwner().tryGetMethod(getterName).isPresent()) {
451                         return;
452                     }
453 
454                     getterName = "is" + method.getName().substring(3);
455                     if (method.getOwner().tryGetMethod(getterName).isPresent()) {
456                         return;
457                     }
458 
459                     events.add(SimpleConditionEvent.violated(method,
460                             "No matching JsxGetter found for " + method.getFullName()));
461                 }
462             };
463 
464     /**
465      * Every @JsxSetter needs a matching @JsxGetter.
466      */
467     @ArchTest
468     public static final ArchRule setterNeedsMatchingGetter = methods()
469             .that().areAnnotatedWith(JsxSetter.class)
470             .should(hasMatchingGetter);
471 
472     /**
473      * Do not overwrite toString for javascript, use jsToString and define the
474      * functionName in the @JsxFunction annotation.
475      */
476     @ArchTest
477     public static final ArchRule jsToString = methods()
478             .that().areAnnotatedWith(JsxFunction.class)
479             .should().haveNameNotMatching("toString");
480 
481     /**
482      * Make sure to not use java.lang.reflect.Executable.
483      */
484     @ArchTest
485     public static final ArchRule android7Executable = noClasses()
486             .should().dependOnClassesThat().haveFullyQualifiedName(Executable.class.getName());
487 
488     /**
489      * Make sure to not use {@link ThreadLocal#withInitial(java.util.function.Supplier)}.
490      */
491     @ArchTest
492     public static final ArchRule android7ThreadLocalWithInitial = noClasses()
493             .should().callMethod(ThreadLocal.class, "withInitial", Supplier.class);
494 
495     /**
496      * Make sure to not use org.w3c.dom.traversal.TreeWalker.
497      */
498     @ArchTest
499     public static final ArchRule androidTreeWalker = noClasses()
500             .that()
501                 .doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlDomTreeWalker")
502                 .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.dom.traversal.DomTreeWalker")
503                 .and().doNotHaveFullyQualifiedName("org.htmlunit.SgmlPage")
504             .should().dependOnClassesThat().haveFullyQualifiedName("org.w3c.dom.traversal.TreeWalker");
505 
506     /**
507      * Make sure to not use org.w3c.dom.traversal.DocumentTraversal.
508      */
509     @ArchTest
510     public static final ArchRule androidDocumentTraversal = noClasses()
511             .should().dependOnClassesThat().haveFullyQualifiedName("org.w3c.dom.traversal.DocumentTraversal");
512 
513     /**
514      * Make sure to not use javax.imageio.
515      */
516     @ArchTest
517     public static final ArchRule androidRanges = noClasses()
518         .should().dependOnClassesThat().resideInAnyPackage("org.w3c.dom.ranges..");
519 
520 
521     /**
522      * Make sure to not use javax.imageio.
523      */
524     @ArchTest
525     public static final ArchRule androidImageio = noClasses()
526          .that()
527             .doNotHaveFullyQualifiedName("org.htmlunit.platform.image.ImageIOImageData")
528             .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.canvas.rendering.AwtRenderingBackend")
529             .and().doNotHaveFullyQualifiedName("org.htmlunit.platform.canvas.rendering.AwtRenderingBackend")
530             .and().resideOutsideOfPackage("org.htmlunit.jetty..")
531         .should().dependOnClassesThat().resideInAnyPackage("javax.imageio..");
532 
533     /**
534      * Make sure to not use Xerces.
535      */
536     @ArchTest
537     public static final ArchRule xerces = noClasses()
538         .that()
539             .doNotHaveFullyQualifiedName("org.htmlunit.platform.util.XmlUtilsXercesHelper")
540         .should().dependOnClassesThat().resideInAnyPackage("org.apache.xerces..");
541 
542     /**
543      * Make sure to not use jdk - Xerces.
544      */
545     @ArchTest
546     public static final ArchRule jdkXerces = noClasses()
547         .that()
548             .doNotHaveFullyQualifiedName("org.htmlunit.platform.util.XmlUtilsSunXercesHelper")
549         .should().dependOnClassesThat().resideInAnyPackage("com.sun.org.apache.xerces..");
550 
551     /**
552      * Make sure the httpclient is only accessed from the adapter classes.
553      */
554     @ArchTest
555     public static final ArchRule httpClient = noClasses()
556         .that()
557             .doNotHaveFullyQualifiedName("org.htmlunit.HttpWebConnection")
558             .and().areNotInnerClasses()
559             .and().areNotMemberClasses()
560 
561             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebClient")
562             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebRequest")
563             .and().doNotHaveFullyQualifiedName("org.htmlunit.util.Cookie")
564             .and().doNotHaveFullyQualifiedName("org.htmlunit.DefaultCredentialsProvider")
565 
566             .and().resideOutsideOfPackage("org.htmlunit.httpclient..")
567         .should().dependOnClassesThat().resideInAnyPackage("org.apache.http..");
568 
569     /**
570      * Make sure the HttpWebConnection is the only entry into the HttpClient adapter.
571      */
572     @ArchTest
573     public static final ArchRule httpWebConnection = noClasses()
574         .that()
575             .doNotHaveFullyQualifiedName("org.htmlunit.HttpWebConnection")
576             .and().areNotInnerClasses()
577             .and().areNotMemberClasses()
578 
579             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebClient")
580             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebRequest")
581             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.xml.XMLHttpRequest")
582             .and().doNotHaveFullyQualifiedName("org.htmlunit.DefaultCredentialsProvider")
583 
584             .and().resideOutsideOfPackage("org.htmlunit.httpclient..")
585         .should().dependOnClassesThat().resideInAnyPackage("org.htmlunit.httpclient..");
586 
587     /**
588      * Do not use core-js dependencies outside of the adapter.
589      */
590     @ArchTest
591     public static final ArchRule corejsPackageRule = noClasses()
592         .that()
593             .doNotHaveFullyQualifiedName("org.htmlunit.WebConsole")
594             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebConsole$1")
595             .and().doNotHaveFullyQualifiedName("org.htmlunit.ScriptException")
596             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.DomElement")
597             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlDialog")
598             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlDialog$1")
599             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlInput")
600             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlPage")
601             .and().doNotHaveFullyQualifiedName("org.htmlunit.util.WebClientUtils")
602 
603             .and().resideOutsideOfPackage("org.htmlunit.javascript..")
604 
605             .and().resideOutsideOfPackage("org.htmlunit.corejs..")
606         .should().dependOnClassesThat().resideInAnyPackage("org.htmlunit.corejs..");
607 
608     /**
609      * Do not use core-js ScriptRuntime outside of the JavaScriptEngine.
610      */
611     @ArchTest
612     public static final ArchRule corejsScriptRuntimeRule = noClasses()
613         .that()
614             .doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.URLSearchParams")
615 
616             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.HtmlUnitContextFactory")
617             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.JavaScriptEngine")
618             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.JavaScriptEngine$2")
619             .and().resideOutsideOfPackage("org.htmlunit.corejs..")
620 
621         .should().dependOnClassesThat().haveFullyQualifiedName("org.htmlunit.corejs.javascript.ScriptRuntime");
622 
623     /**
624      * Do not use core-js org.htmlunit.corejs.javascript.Undefined.instance directly.
625      */
626     @ArchTest
627     public static final ArchRule corejsUndefinedRule = noClasses()
628         .that()
629             .doNotHaveFullyQualifiedName("org.htmlunit.javascript.JavaScriptEngine")
630             .and().resideOutsideOfPackage("org.htmlunit.corejs..")
631 
632         .should().dependOnClassesThat().haveFullyQualifiedName("org.htmlunit.corejs.javascript.Undefined");
633 
634     /**
635      * Do not use core-js ScriptRuntime outside of the JavaScriptEngine.
636      */
637     @ArchTest
638     public static final ArchRule javaScriptEngineRule = noClasses()
639         .that()
640             .resideOutsideOfPackage("org.htmlunit.javascript..")
641 
642             .and().doNotHaveFullyQualifiedName("org.htmlunit.WebClient")
643             .and().doNotHaveFullyQualifiedName("org.htmlunit.ScriptResult")
644             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.DomElement")
645             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlPage")
646             .and().doNotHaveFullyQualifiedName("org.htmlunit.util.DebuggingWebConnection")
647 
648         .should().dependOnClassesThat().haveFullyQualifiedName("org.htmlunit.javascript.JavaScriptEngine");
649 
650     /**
651      * Do not use jetty.
652      */
653     @ArchTest
654     public static final ArchRule jettyPackageRule = noClasses()
655         .should().dependOnClassesThat().resideInAnyPackage("org.eclipse.jetty..");
656 
657 
658     /**
659      * Some methods should not be used.
660      */
661     @ArchTest
662     public static final ArchRule forbidObjectsRequireNonNull = noClasses()
663         .that()
664             .resideOutsideOfPackage("org.htmlunit.corejs..")
665 
666             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.DomNode") // strange but reports method fireAddition() on jenkins
667             .and().doNotHaveFullyQualifiedName("org.htmlunit.html.HtmlPage") // strange but reports method notifyNodeAdded() on jenkins
668 
669             .and().resideOutsideOfPackage("org.htmlunit.jetty..")
670 
671         .should().callMethod(Objects.class, "requireNonNull", Object.class); // Objects.requireNonNull(Object) is forbidden, always add a message
672 }