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,
+ *
+ * - while developing the project in an IDE
+ * - while running Maven lifecycle goals or
+ * - while executing or referencing the project at run-time, whether packaged or not.
+ *
.
+ *
+ * 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);
}
}