Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,23 @@ extraJavaModuleInfo {
module("com.github.javaparser:javaparser-symbol-solver-core", "com.github.javaparser.symbolsolver.core")
module("net.sf.jopt-simple:jopt-simple", "net.sf.jopt.simple")

module("com.tngtech.archunit:archunit-junit5-api", "com.tngtech.archunit.junit5.api")
module("com.tngtech.archunit:archunit-junit5-engine", "com.tngtech.archunit.junit5.engine")
module("com.tngtech.archunit:archunit-junit5-engine-api", "com.tngtech.archunit.junit5.engineapi")
module("com.tngtech.archunit:archunit-junit5-api", "com.tngtech.archunit.junit5.api") {
exportAllPackages()
requireAllDefinedDependencies()
}
module("com.tngtech.archunit:archunit-junit5-engine", "com.tngtech.archunit.junit5.engine") {
exportAllPackages()
requireAllDefinedDependencies()
}
module("com.tngtech.archunit:archunit-junit5-engine-api", "com.tngtech.archunit.junit5.engineapi") {
exportAllPackages()
requireAllDefinedDependencies()
}
module("com.tngtech.archunit:archunit", "com.tngtech.archunit") {
exportAllPackages()
requireAllDefinedDependencies()
requires("java.logging")
requires("org.slf4j")
uses("com.tngtech.archunit.lang.extension.ArchUnitExtension")
}

module("org.glassfish.hk2.external:aopalliance-repackaged", "org.aopalliance")
Expand Down
4 changes: 2 additions & 2 deletions docs/code-howtos/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ Check the directory `jablib/src/main/resources/csl-locales`.
If it is missing or empty, run `git submodule update`.
If still not fixed, run `git reset --hard` **inside that directory**.

### `org.jabref.architecture.MainArchitectureTest restrictStandardStreams` <span style="color:red">FAILED</span>
### `org.jabref.support.CommonArchitectureTest restrictStandardStreams` <span style="color:red">FAILED</span>

Check if you've used `System.out.println(...)` (the standard output stream) to log anything into the console.
This is an architectural violation, as you should use the Logger instead for logging.
More details on [how to log](https://devdocs.jabref.org/code-howtos/logging.html).

### `org.jabref.architecture.MainArchitectureTest doNotUseLogicInModel` <span style="color:red">FAILED</span>
### `org.jabref.support.CommonArchitectureTest doNotUseLogicInModel` <span style="color:red">FAILED</span>

One common case when this test fails is when you put any class purely containing business logic inside the `model` package (i.e., inside the directory `org/jabref/model/`).
To fix this, shift the class to a sub-package within the `logic` package (i.e., the directory`org/jabref/logic/`).
Expand Down
7 changes: 7 additions & 0 deletions jabgui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ dependencies {

testImplementation("com.github.javaparser:javaparser-symbol-solver-core")
testImplementation("org.ow2.asm:asm")

testImplementation("com.tngtech.archunit:archunit")
testImplementation("com.tngtech.archunit:archunit-junit5-api")
testRuntimeOnly("com.tngtech.archunit:archunit-junit5-engine")
}

application {
Expand Down Expand Up @@ -218,6 +222,9 @@ javaModuleTesting.whitebox(testing.suites["test"]) {

requires.add("wiremock")
requires.add("wiremock.slf4j.spi.shim")

requires.add("com.tngtech.archunit")
requires.add("com.tngtech.archunit.junit5.api")
}

tasks.test {
Expand Down
2 changes: 1 addition & 1 deletion jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@AllowedToUseAwt("Requires ava.awt.datatransfer.Clipboard")
@AllowedToUseAwt("Requires java.awt.datatransfer.Clipboard")
public class ClipBoardManager {

public static final DataFormat XML = new DataFormat("application/xml");
Expand Down
10 changes: 10 additions & 0 deletions jabgui/src/test/java/org/jabref/gui/JabGuiArchitectureTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.jabref.gui;

import org.jabref.support.CommonArchitectureTest;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;

@AnalyzeClasses(packages = "org.jabref", importOptions = ImportOption.DoNotIncludeTests.class)
public class JabGuiArchitectureTests extends CommonArchitectureTest {
}
7 changes: 6 additions & 1 deletion jablib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,10 @@ dependencies {
testImplementation("org.xmlunit:xmlunit-core")
testImplementation("org.xmlunit:xmlunit-matchers")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("com.tngtech.archunit:archunit-junit5-engine")

testImplementation("com.tngtech.archunit:archunit")
testImplementation("com.tngtech.archunit:archunit-junit5-api")
testRuntimeOnly("com.tngtech.archunit:archunit-junit5-engine")

testImplementation("org.hamcrest:hamcrest")

Expand Down Expand Up @@ -563,4 +565,7 @@ javaModuleTesting.whitebox(testing.suites["test"]) {
requires.add("org.xmlunit.matchers")
requires.add("wiremock")
requires.add("wiremock.slf4j.spi.shim")

requires.add("com.tngtech.archunit")
requires.add("com.tngtech.archunit.junit5.api")
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
import kong.unirest.core.json.JSONArray;
import kong.unirest.core.json.JSONException;
import kong.unirest.core.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EuropePmcFetcher implements IdBasedParserFetcher {
private static final Logger LOGGER = LoggerFactory.getLogger(EuropePmcFetcher.class);

@Override
public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException {
return URI.create("https://www.ebi.ac.uk/europepmc/webservices/rest/search?query=" + identifier + "&resultType=core&format=json").toURL();
return new URI("https://www.ebi.ac.uk/europepmc/webservices/rest/search?query=" + identifier + "&resultType=core&format=json").toURL();
}

@Override
Expand All @@ -49,7 +52,7 @@ private BibEntry jsonItemToBibEntry(JSONObject item) throws ParseException {
try {
JSONObject result = item.getJSONObject("resultList").getJSONArray("result").getJSONObject(0);

System.out.println(result.toString(2));
LOGGER.debug(result.toString(2));

EntryType entryType = StandardEntryType.Article;
if (result.has("pubTypeList")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;

import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -75,12 +77,17 @@ public static InetAddress getIpAddress() throws UnknownHostException {
return InetAddress.getByName("localhost");
}

public URI getHttpServerUri() {
public @NonNull URI getHttpServerUri() {
try {
return URI.create("http://" + RemotePreferences.getIpAddress().getHostAddress() + ":23119");
} catch (UnknownHostException e) {
return new URI("http://" + RemotePreferences.getIpAddress().getHostAddress() + ":23119");
} catch (UnknownHostException | URISyntaxException e) {
LOGGER.error("Could not create HTTP server URI. Falling back to default.", e);
return URI.create("http://localhost:23119");
try {
return new URI("http://localhost:23119");
} catch (URISyntaxException ex) {
LOGGER.error("Should never happen, raw string is already valid uri");
throw new RuntimeException(ex);
}
}
}
}
2 changes: 2 additions & 0 deletions jablib/src/main/java/org/jabref/model/strings/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import javafx.util.Pair;

import org.jabref.architecture.AllowedToUseApacheCommonsLang3;
import org.jabref.architecture.AllowedToUseLogic;
import org.jabref.logic.bibtex.FieldWriter;
import org.jabref.logic.os.OS;

Expand All @@ -23,6 +24,7 @@

@SuppressWarnings("checkstyle:NoMultipleClosingBracesAtEndOfLine")
@AllowedToUseApacheCommonsLang3("There is no equivalent in Google's Guava")
@AllowedToUseLogic("OS.NewLine is most basic")
public class StringUtil {

// Non-letters which are used to denote accents in LaTeX-commands, e.g., in {\"{a}}
Expand Down
10 changes: 10 additions & 0 deletions jablib/src/test/java/org/jabref/logic/JabLibArchitectureTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.jabref.logic;

import org.jabref.support.CommonArchitectureTest;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;

@AnalyzeClasses(packages = "org.jabref", importOptions = ImportOption.DoNotIncludeTests.class)
public class JabLibArchitectureTests extends CommonArchitectureTest {
}
4 changes: 2 additions & 2 deletions jabsrv/src/main/java/org/jabref/http/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public HttpServer run(List<Path> files, URI uri) {
// GUI uses HttpServerManager
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
System.out.println("Shutting down jabsrv...");
LOGGER.debug("Shutting down jabsrv...");
httpServer.shutdownNow();
System.out.println("Done, exit.");
LOGGER.debug("Done, exit.");
} catch (Exception e) {
LOGGER.error("Could not shut down server", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import javafx.application.Platform;

import org.jabref.architecture.AllowedToUseAwt;
import org.jabref.http.server.cayw.format.FormatterService;
import org.jabref.http.server.cayw.gui.CAYWEntry;
import org.jabref.http.server.cayw.gui.SearchDialog;
Expand All @@ -40,6 +41,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@AllowedToUseAwt("Requires java.awt.datatransfer.Clipboard")
@Path("better-bibtex/cayw")
public class CAYWResource {
private static final Logger LOGGER = LoggerFactory.getLogger(CAYWResource.class);
Expand Down
3 changes: 3 additions & 0 deletions test-support/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ dependencies {
implementation("net.bytebuddy:byte-buddy")

implementation("org.jspecify:jspecify")

implementation("com.tngtech.archunit:archunit")
implementation("com.tngtech.archunit:archunit-junit5-api")
}
3 changes: 3 additions & 0 deletions test-support/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
requires org.junit.jupiter.api;
requires org.mockito;
requires org.jabref.jablib;
requires com.tngtech.archunit;
requires com.tngtech.archunit.junit5.api;
requires javafx.graphics;

exports org.jabref.support;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package org.jabref.support;

import java.net.URI;
import java.nio.file.Paths;

import org.jabref.architecture.AllowedToUseApacheCommonsLang3;
import org.jabref.architecture.AllowedToUseAwt;
import org.jabref.architecture.AllowedToUseClassGetResource;
import org.jabref.architecture.AllowedToUseLogic;
import org.jabref.architecture.AllowedToUseStandardStreams;
import org.jabref.architecture.AllowedToUseSwing;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.GeneralCodingRules;

/**
* This class checks JabRef's shipped classes for architecture quality.
* Does not analyze test classes. Hint from <a href="https://stackoverflow.com/a/44681895/873282">StackOverflow</a>
*/
@AnalyzeClasses(packages = "org.jabref", importOptions = ImportOption.DoNotIncludeTests.class)
public class CommonArchitectureTest {

private static final String PACKAGE_JAVAX_SWING = "javax.swing..";
private static final String PACKAGE_JAVA_AWT = "java.awt..";
private static final String PACKAGE_ORG_JABREF_GUI = "org.jabref.gui..";
private static final String PACKAGE_ORG_JABREF_LOGIC = "org.jabref.logic..";
private static final String PACKAGE_ORG_JABREF_MODEL = "org.jabref.model..";
private static final String PACKAGE_ORG_JABREF_CLI = "org.jabref.cli..";

@ArchTest
public void doNotUseApacheCommonsLang3(JavaClasses classes) {
ArchRuleDefinition.noClasses().that().areNotAnnotatedWith(AllowedToUseApacheCommonsLang3.class)
.should().accessClassesThat().resideInAPackage("org.apache.commons.lang3")
.check(classes);
}

@ArchTest
public void doNotUseSwing(JavaClasses classes) {
// This checks for all Swing packages, but not the UndoManager
ArchRuleDefinition.noClasses().that().areNotAnnotatedWith(AllowedToUseSwing.class)
.should().accessClassesThat()
.resideInAnyPackage("javax.swing",
"javax.swing.border..",
"javax.swing.colorchooser..",
"javax.swing.event..",
"javax.swing.filechooser..",
"javax.swing.plaf..",
"javax.swing.table..",
"javax.swing.text..",
"javax.swing.tree..")
.check(classes);
}

@ArchTest
public void doNotUseAssertJ(JavaClasses classes) {
ArchRuleDefinition.noClasses().should().accessClassesThat().resideInAPackage("org.assertj..")
.check(classes);
}

@ArchTest
public void doNotUseJavaAWT(JavaClasses classes) {
ArchRuleDefinition.noClasses().that().areNotAnnotatedWith(AllowedToUseAwt.class)
.should().accessClassesThat().resideInAPackage(PACKAGE_JAVA_AWT)
.check(classes);
}

@ArchTest
public void doNotUsePaths(JavaClasses classes) {
ArchRuleDefinition.noClasses().should()
.accessClassesThat()
.belongToAnyOf(Paths.class)
.because("Path.of(...) should be used instead")
.check(classes);
}

@ArchTest
public void useStreamsOfResources(JavaClasses classes) {
// Reason: https://github.com/oracle/graal/issues/7682#issuecomment-1786704111
ArchRuleDefinition.noClasses().that().haveNameNotMatching(".*Test")
.and().areNotAnnotatedWith(AllowedToUseClassGetResource.class)
.and().areNotAssignableFrom("org.jabref.logic.importer.fileformat.ImporterTestEngine")
.should()
.callMethod(Class.class, "getResource", String.class)
.because("getResourceAsStream(...) should be used instead")
.check(classes);
}

// TODO: no org.jabref.gui package may reside in org.jabref.logic

@ArchTest
public void doNotUseLogicInModel(JavaClasses classes) {
ArchRuleDefinition.noClasses().that().resideInAPackage(PACKAGE_ORG_JABREF_MODEL)
.and().areNotAnnotatedWith(AllowedToUseLogic.class)
.should().dependOnClassesThat().resideInAPackage(PACKAGE_ORG_JABREF_LOGIC)
.check(classes);
}

@ArchTest
public void restrictUsagesInModel(JavaClasses classes) {
// Until we switch to Lucene, we need to access Globals.stateManager().getActiveDatabase() from the search classes,
// because the PDFSearch needs to access the index of the corresponding database
ArchRuleDefinition.noClasses().that().areNotAssignableFrom("org.jabref.model.search.rules.ContainBasedSearchRule")
.and().areNotAssignableFrom("org.jabref.model.search.rules.RegexBasedSearchRule")
.and().areNotAssignableFrom("org.jabref.model.search.rules.GrammarBasedSearchRule")
.and().resideInAPackage(PACKAGE_ORG_JABREF_MODEL)
.should().dependOnClassesThat().resideInAPackage(PACKAGE_JAVAX_SWING)
.check(classes);
}

@ArchTest
public void restrictUsagesInLogic(JavaClasses classes) {
ArchRuleDefinition.noClasses().that().resideInAPackage(PACKAGE_ORG_JABREF_LOGIC)
.and().areNotAnnotatedWith(AllowedToUseSwing.class)
.and().areNotAssignableFrom("org.jabref.logic.search.DatabaseSearcherWithBibFilesTest")
.should().dependOnClassesThat().resideInAPackage(PACKAGE_JAVAX_SWING)
.check(classes);
}

@ArchTest
public void restrictStandardStreams(JavaClasses classes) {
ArchRuleDefinition.noClasses().that().resideOutsideOfPackages(PACKAGE_ORG_JABREF_CLI)
.and().resideOutsideOfPackages("org.jabref.gui.openoffice..") // Uses LibreOffice SDK
.and().areNotAnnotatedWith(AllowedToUseStandardStreams.class)
.should(GeneralCodingRules.ACCESS_STANDARD_STREAMS)
.because("logging framework should be used instead or the class be marked explicitly as @AllowedToUseStandardStreams")
.check(classes);
}

/// Use constructor new URI(...) instead
@ArchTest
public void shouldNotCallUriCreateMethod(JavaClasses classes) {
ArchRuleDefinition.noClasses()
.that()
.resideInAPackage("org.jabref..")
.should().callMethod(URI.class, "create", String.class)
.check(classes);
}
}
1 change: 1 addition & 0 deletions versions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies.constraints {
api("com.squareup.okhttp3:okhttp:4.12.0")
api("com.squareup.okio:okio-jvm:3.12.0")
api("com.squareup.retrofit2:retrofit:3.0.0")
api("com.tngtech.archunit:archunit:1.4.1")
api("com.tngtech.archunit:archunit-junit5-api:1.4.1")
api("com.tngtech.archunit:archunit-junit5-engine:1.4.1")
api("com.vladsch.flexmark:flexmark-html2md-converter:0.64.8")
Expand Down