diff --git a/src/main/java/net/ucanaccess/exception/UcanaccessSQLException.java b/src/main/java/net/ucanaccess/exception/UcanaccessSQLException.java index 5ea8d1ce..0a7fdd14 100644 --- a/src/main/java/net/ucanaccess/exception/UcanaccessSQLException.java +++ b/src/main/java/net/ucanaccess/exception/UcanaccessSQLException.java @@ -1,6 +1,7 @@ package net.ucanaccess.exception; import net.ucanaccess.jdbc.IUcanaccessErrorCodes; +import net.ucanaccess.util.VersionInfo; import org.hsqldb.error.ErrorCode; import java.sql.SQLException; @@ -75,11 +76,13 @@ String addVersionInfo(String _message) { if (_message != null && _message.startsWith(MSG_PREFIX)) { return _message; } - return MSG_PREFIX + + String ver = VersionInfo.find(getClass()).getVersion(); + return (MSG_PREFIX + "::" - + Optional.ofNullable(getClass().getPackage().getImplementationVersion()).orElse("5.1.0") + + Optional.ofNullable(ver).orElse("x.y.z") + " " - + (_message == null || _message.isBlank() ? "(n/a)" : _message.trim()); + + (_message == null || _message.isBlank() ? "(n/a)" : _message)).trim(); } @Override diff --git a/src/main/java/net/ucanaccess/jdbc/UcanaccessDriver.java b/src/main/java/net/ucanaccess/jdbc/UcanaccessDriver.java index 7aa0b03c..e77163a4 100644 --- a/src/main/java/net/ucanaccess/jdbc/UcanaccessDriver.java +++ b/src/main/java/net/ucanaccess/jdbc/UcanaccessDriver.java @@ -11,6 +11,7 @@ import net.ucanaccess.exception.UcanaccessSQLException; import net.ucanaccess.type.ColumnOrder; import net.ucanaccess.util.Try; +import net.ucanaccess.util.VersionInfo; import java.io.File; import java.io.IOException; @@ -252,12 +253,12 @@ private Integer validateLobScale(String _property) { @Override public int getMajorVersion() { - return 0; + return VersionInfo.find(getClass()).getMajorVersion(); } @Override public int getMinorVersion() { - return 0; + return VersionInfo.find(getClass()).getMinorVersion(); } @Override diff --git a/src/main/java/net/ucanaccess/util/VersionInfo.java b/src/main/java/net/ucanaccess/util/VersionInfo.java new file mode 100644 index 00000000..29c7cae1 --- /dev/null +++ b/src/main/java/net/ucanaccess/util/VersionInfo.java @@ -0,0 +1,155 @@ +package net.ucanaccess.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * The purpose of this class is to find the project's version information by all means possible, + * . + * + * First this class attempts to read version information from key {@code Attributes.Name.IMPLEMENTATION_VERSION} + * in the Java manifest by using the Java api. + *

+ * Should this operation fail, it looks for a {@code MANIFEST.MF} in the file system in order to + * read version information from the first file found. + *

+ * In case this also fails, it looks for the project's Maven POM file to extraxct version information from the xml structure. + * + * @author Markus Spann + * @since v5.1.2 + */ +public final class VersionInfo { + + /** Cash of version information by class. */ + private static final Map, VersionInfo> CACHE = new ConcurrentHashMap<>(); + + private final Logger logger; + + private String version; + private int majorVersion; + private int minorVersion; + + public static VersionInfo find(Class _class) { + return CACHE.computeIfAbsent(_class, VersionInfo::new); + } + + private VersionInfo(Class _class) { + logger = System.getLogger(getClass().getName()); + version = Objects.requireNonNullElse(_class, getClass()).getPackage().getImplementationVersion(); + if (version != null) { + logger.log(Level.DEBUG, "Found version ''{0}'' in package", version); + } else { + version = readFromManifest(); + if (version == null) { + version = readFromMavenPom(); + } + } + + if (version != null) { + Matcher matcher = Pattern.compile("^([0-9]+)\\.([0-9]+).*").matcher(version); + if (matcher.matches()) { + majorVersion = Integer.valueOf(matcher.group(1)); + minorVersion = Integer.valueOf(matcher.group(2)); + } else { + logger.log(Level.WARNING, "Unparsable implementation version: {0}", version); + } + } + } + + String readFromManifest() { + String filename = "MANIFEST.MF"; + Path currDir = Paths.get("").toAbsolutePath(); + logger.log(Level.DEBUG, "Searching manifest under current directory ''{0}''", currDir); + try (Stream walk = Files.walk(currDir)) { + Path manifest = walk.filter(p -> p.getFileName().toString().equals(filename)).findFirst().orElse(null); + if (manifest == null) { + logger.log(Level.DEBUG, "Could not find ''{0}'' in path ''{1}''", filename, currDir); + } else { + try (InputStream is = Files.newInputStream(manifest, StandardOpenOption.READ)) { + String ver = new Manifest(is).getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); + logger.log(Level.DEBUG, "Found version ''{0}'' in manifest", ver); + return ver; + } + } + } catch (IOException _ex) { + logger.log(Level.WARNING, "Failed to find version info in manifest: {0}", _ex.toString()); + } + return null; + } + + String readFromMavenPom() { + String filename = "pom.xml"; + Path currDir = Paths.get("").toAbsolutePath(); + logger.log(Level.DEBUG, "Searching maven pom under current directory ''{0}''", currDir); + try (Stream walk = Files.walk(currDir)) { + Path pom = walk.filter(p -> p.getFileName().toString().equals(filename)).findFirst().orElse(null); + if (pom == null) { + logger.log(Level.WARNING, "Failed to find ''{0}'' in path ''{1}''", filename, currDir); + } else { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pom.toFile()); + + Element docElem = document.getDocumentElement(); + docElem.normalize(); + + NodeList nodeList = docElem.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node instanceof Element && "version".equals(node.getNodeName())) { + String ver = node.getTextContent(); + logger.log(Level.DEBUG, "Found version ''{0}'' in maven pom", ver); + return ver; + } + } + } + } catch (IOException | ParserConfigurationException | SAXException _ex) { + logger.log(Level.WARNING, "Failed to find version info in maven pom: {0}", _ex.toString()); + } + return null; + } + + public String getVersion() { + return version; + } + + public int getMajorVersion() { + return majorVersion; + } + + public int getMinorVersion() { + return minorVersion; + } + + @Override + public String toString() { + return String.format("%s[version=%s, major=%d, minor=%d]", + getClass().getSimpleName(), version, majorVersion, minorVersion); + } + +} diff --git a/src/test/java/net/ucanaccess/exception/UcanaccessSQLExceptionTest.java b/src/test/java/net/ucanaccess/exception/UcanaccessSQLExceptionTest.java index e42d4ae9..94f8f977 100644 --- a/src/test/java/net/ucanaccess/exception/UcanaccessSQLExceptionTest.java +++ b/src/test/java/net/ucanaccess/exception/UcanaccessSQLExceptionTest.java @@ -1,20 +1,23 @@ package net.ucanaccess.exception; +import static org.assertj.core.api.Assertions.assertThat; + import net.ucanaccess.test.AbstractBaseTest; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -class UcanaccessSQLExceptionTest extends AbstractBaseTest { +class UcanaccessSQLExceptionTest extends AbstractBaseTest { @ParameterizedTest(name = "[{index}] {0} => {1}") - @CsvSource(delimiter = ';', value = { - "UCAExc:; UCAExc:", - "bogus; UCAExc:::5.1.0 bogus", - "''; UCAExc:::5.1.0 (n/a)", - "; UCAExc:::5.1.0 (n/a)" + @CsvSource(delimiter = '|', value = { + "UCAExc:| UCAExc:", + "bogus| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? bogus", + "''| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? \\(n\\/a\\)", + "| UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? \\(n\\/a\\)" }) - void testAddVersionInfo(String _message, CharSequence _expectedResult) { - assertEquals(_expectedResult, new UcanaccessSQLException().addVersionInfo(_message)); + void testAddVersionInfo(String _message, CharSequence _expectedPattern) { + assertThat(new UcanaccessSQLException().addVersionInfo(_message)) + .matches(_expectedPattern); } } diff --git a/src/test/java/net/ucanaccess/jdbc/UcanaccessDriverTest.java b/src/test/java/net/ucanaccess/jdbc/UcanaccessDriverTest.java index 3cd96f06..66a128a4 100644 --- a/src/test/java/net/ucanaccess/jdbc/UcanaccessDriverTest.java +++ b/src/test/java/net/ucanaccess/jdbc/UcanaccessDriverTest.java @@ -34,4 +34,12 @@ void testNormalizeProperties() { .containsEntry("withoutVal2", null); } + @Test + void testVersion() { + UcanaccessDriver driver = new UcanaccessDriver(); + + assertThat(driver.getMajorVersion()).isGreaterThanOrEqualTo(5); + assertThat(driver.getMinorVersion()).isGreaterThanOrEqualTo(1); + } + } diff --git a/src/test/java/net/ucanaccess/jdbc/UnproperExecuteQueryTest.java b/src/test/java/net/ucanaccess/jdbc/UnproperExecuteQueryTest.java index b505c9c0..c9277edb 100644 --- a/src/test/java/net/ucanaccess/jdbc/UnproperExecuteQueryTest.java +++ b/src/test/java/net/ucanaccess/jdbc/UnproperExecuteQueryTest.java @@ -28,7 +28,7 @@ void testExecute(AccessVersion _accessVersion) throws Exception { "DELETE FROM t_noroman")) { assertThatThrownBy(() -> st.executeQuery(sql)) .isInstanceOf(UcanaccessSQLException.class) - .hasMessageMatching("UCAExc:::[0-9\\.]+ General error"); + .hasMessageMatching("UCAExc:::[0-9]\\.[0-9][0-9\\.]*(?:-SNAPSHOT)? General error"); st.execute(sql); } }