package jmri; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import java.io.File; import org.junit.jupiter.api.*; import com.tngtech.archunit.junit.*; import com.tngtech.archunit.lang.ArchRule; import jmri.util.FileUtil; /** * Check the architecture of the JMRI library *

* This is run as part of CI, so it's expected to kept passing at all times. * It includes some exceptions that have been grandfathered in * via the archunit_ignore_patterns.txt file. *

* Checks that are not yet passing are added in the * {link ArchitectureCheck} class, which can be run independently. *

* Note that this only checks the classes in target/classes, which come from java/src, not * the ones in target/test-classes, which come from java/test. It's relying on the common * build procedure to make this distinction. * See {@link TestArchitectureTest} * * See examples in the * Intentionally redundant with the check for references to * jmri.jmrix outside itself; fix these first! */ @ArchTest // Initially 1 flags in JMRI 4.17.3 - see archunit_ignore_patterns.txt public static final ArchRule checkJmriPackageJmrix = noClasses() .that().resideInAPackage("jmri") .should().dependOnClassesThat().resideInAPackage("jmri.jmrix.."); /** * Jmri.jmris should not reference jmri.jmrix *

* Intentionally redundant with the check for references to * jmri.jmrix outside itself; fix these first! *

* */ @ArchTest public static final ArchRule checkJmrisPackageJmrix = noClasses() .that().resideInAPackage("jmri.jmris") .should().dependOnClassesThat().resideInAPackage("jmri.jmrix.."); /** * Jmri.server should not reference jmri.jmrix *

* Intentionally redundant with the check for references to * jmri.jmrix outside itself; fix these first! *

* */ @ArchTest public static final ArchRule checkServerPackageJmrix = noClasses() .that().resideInAPackage("jmri.server") .should().dependOnClassesThat().resideInAPackage("jmri.jmrix.."); /** * Jmri.server should not reference jmri.jmrit *

* Intentionally redundant with the check for references to * jmri.jmrit outside itself; fix these first! *

* */ @ArchTest public static final ArchRule checkServerPackageJmrit = noClasses() .that().resideInAPackage("jmri.server") .should().dependOnClassesThat().resideInAPackage("jmri.jmrit.."); /** * Jmri.web should not reference jmri.jmrit *

* Intentionally redundant with the check for references to * jmri.jmrit outside itself; fix these first! *

* */ @ArchTest public static final ArchRule checkWebPackageJmrit = noClasses() .that().resideInAPackage("jmri.web") .should().dependOnClassesThat().resideInAPackage("jmri.jmrit.."); /** * Jmri.web should not reference jmri.jmrix *

* Intentionally redundant with the check for references to * jmri.jmrix outside itself; fix these first! *

* */ @ArchTest public static final ArchRule checkWebPackageJmrix = noClasses() .that().resideInAPackage("jmri.web") .should().dependOnClassesThat().resideInAPackage("jmri.jmrix.."); /** * No AWT in basic interfaces. */ @ArchTest // Initially 8 flags in JMRI 4.17.3 - see archunit_ignore_patterns.txt public static final ArchRule checkJmriPackageAwt = noClasses() .that().resideInAPackage("jmri") .should().dependOnClassesThat().resideInAPackage("java.awt.."); /** * No Swing in basic interfaces. */ @ArchTest // Initially 5 flags in JMRI 4.17.3 - see archunit_ignore_patterns.txt public static final ArchRule checkJmriPackageSwing = noClasses() .that().resideInAPackage("jmri") .should().dependOnClassesThat().resideInAPackage("javax.swing.."); /** * No JDOM in basic interfaces. */ @ArchTest // Initially 3 flags in JMRI 4.17.3 - see archunit_ignore_patterns.txt public static final ArchRule checkJmriPackageJdom = noClasses() .that().resideInAPackage("jmri").and() .doNotHaveFullyQualifiedName("jmri.UserPreferencesManager") .should().dependOnClassesThat().resideInAPackage("org.jdom2.."); /** * jmri (but not apps) should not reference org.apache.log4j to allow jmri * to be used as library in applications that choose not to use Log4J. */ @ArchTest public static final ArchRule noLog4JinJmri = noClasses() .that().resideInAPackage("jmri..") .should().dependOnClassesThat().resideInAPackage("org.apache.logging.log4j"); /** * JMRI (but not apps) should use org.slf4j.Logger instead of JUL. */ @ArchTest public static final ArchRule noJULinJmri = noClasses() .that().resideInAPackage("jmri..") .should().dependOnClassesThat().resideInAPackage("java.util.logging"); /** * Confine JDOM to configurexml packages. */ @ArchTest @ArchIgnore // 5792 flags September 2022 public static final ArchRule checkJdomOutsideConfigurexml = noClasses() .that().resideOutsideOfPackage("..configurexml..") .should().accessClassesThat().resideInAPackage("org.jdom2.."); /** * Purejavacomm is the previous serial library. It should not be used * by JMRI java code, but there are scripts that uses it. And there * might be users that have scripts using it. So we need to keep the * library but prevent Java code to use it. */ @ArchTest public static final ArchRule checkPurejavacommUsage = noClasses() .should().accessClassesThat().resideInAPackage("purejavacomm.."); /** * Confine jSerialComm to jmri.jmrix.AbstractSerialPortController with * limited exceptions */ @ArchTest public static final ArchRule checkJSerialCommAllowedUses = noClasses() .that() // all the standard serial access should be confined to here: .doNotHaveFullyQualifiedName("jmri.jmrix.jserialcomm.JSerialPort").and() .doNotHaveFullyQualifiedName("jmri.jmrix.jserialcomm.JSerialPortEvent") .should().accessClassesThat().resideInAPackage("com.fazecast.jSerialComm.."); /** * Check that *Bundle classes inherit from their parent. * (not done yet, not sure how to do it) */ @ArchTest @ArchIgnore // Not complete public static final ArchRule checkBundleInheritance = classes() .that().areAssignableTo(jmri.Bundle.class) .should().haveSimpleNameEndingWith("Bundle"); /** * Check that *Bundle classes are named Bundle */ @ArchTest public static final ArchRule checkBundleNames = classes() .that().areAssignableTo(jmri.Bundle.class) .should().haveSimpleName("Bundle"); /** * Check that classes named *Bundle are Bundles */ @ArchTest public static final ArchRule checkBundleNamesOnlyOnBundleClass = classes() .that().haveSimpleNameEndingWith("Bundle") .should().beAssignableTo(jmri.Bundle.class); /** * No classes in jmri.jmrit.logixng.actions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGActionsNotUsingNamedBeanHandle = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.actions") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionListenOnBeans") // This class doesn't seem to be able to use LogixNG_SelectNamedBean .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionListenOnBeans$NamedBeanReference") // This class doesn't seem to be able to use LogixNG_SelectNamedBean .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionListenOnBeansTable") // This class doesn't seem to be able to use LogixNG_SelectNamedBean .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionListenOnBeansTable$NamedBeanReference") // This class doesn't seem to be able to use LogixNG_SelectNamedBean .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.NamedBeanHandle"); /** * No classes in jmri.jmrit.logixng.actions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGActionsXmlNotUsingNamedBeanHandle = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.actions.configurexml") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.configurexml.ActionListenOnBeansXml") // This class doesn't seem to be able to use LogixNG_SelectNamedBean .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.NamedBeanHandle"); /** * No classes in jmri.jmrit.logixng.expressions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGExpressionsNotUsingNamedBeanHandle = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.expressions") .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.NamedBeanHandle"); /** * No classes in jmri.jmrit.logixng.expressions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGExpressionsXmlNotUsingNamedBeanHandle = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.expressions.configurexml") .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.NamedBeanHandle"); /** * No classes in jmri.jmrit.logixng.actions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGActionsNotUsingBeanSelectPanel = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.actions..") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionCreateBeansFromTableSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionFindTableRowOrColumnSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionListenOnBeansTableSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionLocalVariableSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionMemorySwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionOBlockSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionReporterSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionSetReporterSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionSignalHeadSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionSignalMastSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionTableSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.ActionWarrantSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.swing.TableForEachSwing") // Not converted to Select... yet .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.util.swing.BeanSelectPanel"); /** * No classes in jmri.jmrit.logixng.expressions should access jmri.NamedBeanHandle. * They should use jmri.jmrit.logixng.util.LogixNG_SelectNamedBean instead. */ @ArchTest public static final ArchRule checkLogixNGExpressionsNotUsingBeanSelectPanel = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.expressions..") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.swing.ExpressionLocalVariableSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.swing.ExpressionMemorySwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.swing.ExpressionReporterSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.swing.ExpressionSignalHeadSwing") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.swing.ExpressionSignalMastSwing") // Not converted to Select... yet .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.util.swing.BeanSelectPanel"); /** * No classes in jmri.jmrit.logixng.actions should access jmri.jmrit.logixng.NamedBeanAddressing. * They should use jmri.jmrit.logixng.util.LogixNG_Select* instead. */ @ArchTest public static final ArchRule checkLogixNGActionsNotUsingNamedBeanAddressing = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.actions") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionDispatcher") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionDispatcher$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionLight") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionLight$1") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionOBlock") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionOBlock$1") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionReporter") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionReporter$1") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionScript") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionScript$1") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSignalHead") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSignalHead$1") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSignalMast") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSignalMast$1") // Oper and data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSound") // "Sound" not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionSound$1") // "Sound" not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionWarrant") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ActionWarrant$1") // Data not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ExecuteDelayed") // State not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ExecuteDelayed$1") // State not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.ExecuteDelayed$2") // State not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.TableForEach") // "Row or column converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.actions.TableForEach$1") // "Row or column converted to Select... yet .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.jmrit.logixng.NamedBeanAddressing"); /** * No classes in jmri.jmrit.logixng.actions should access jmri.jmrit.logixng.NamedBeanAddressing. * They should use jmri.jmrit.logixng.util.LogixNG_Select* instead. */ @ArchTest public static final ArchRule checkLogixNGExpressionsNotUsingNamedBeanAddressing = noClasses() .that() .resideInAPackage("jmri.jmrit.logixng.expressions") .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionAudio") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionAudio$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionConditional") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionConditional$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionDispatcher") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionDispatcher$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionEntryExit") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionEntryExit$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionLight") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionLight$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionOBlock") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionOBlock$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionScript") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionScript$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionSignalHead") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionSignalHead$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionSignalMast") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionSignalMast$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionTurnout") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionTurnout$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionWarrant") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.ExpressionWarrant$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.Timer") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.Timer$1") // Not converted to Select... yet .and().doNotHaveFullyQualifiedName("jmri.jmrit.logixng.expressions.Timer$2") // Not converted to Select... yet .should() .dependOnClassesThat().haveFullyQualifiedName("jmri.jmrit.logixng.NamedBeanAddressing"); @Test public void testHelpFileNamesUseShtml(){ String path = FileUtil.getExternalFilename(FileUtil.PROGRAM + "help"); // allow // local/index.html // local/stub_template.html // /local/stub/ String[] allowList = {"local"+File.separator+ "index.html", "local"+File.separator+ "stub_template.html", File.separator + "local" + File.separator + "stub" + File.separator }; recursivelyCheckFiles(new File(path), allowList, ".html"); } private void recursivelyCheckFiles(File directory, String[] allowList, String deniedsuffix) { File[] files = directory.listFiles(); if (files == null) { Assertions.fail("Failed to list files in the directory: " + directory.getAbsolutePath()); return; } for (File file : files) { if (file.isDirectory()) { recursivelyCheckFiles(file, allowList, deniedsuffix); } else { String fname = file.getAbsolutePath(); if (fname.endsWith(deniedsuffix) && notOnAllowList(fname,allowList)) { // System.out.println("Incorrect fileType: "+fname); Assertions.fail("filename " +fname+ " should not end with "+deniedsuffix); } } } } private boolean notOnAllowList(@javax.annotation.Nonnull String filePath, String[] allowList) { for (String allowed : allowList) { if ( filePath.contains(allowed) ) { return false; } } return true; } }