Repository: pgjdbc/pgjdbc (java)

Issue:
Illegal reflective access operation warning with Java 9
Here's warning from Java 9:
```
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.postgresql.jdbc.TimestampUtils (file:.../postgresql-42.1.4.jar) to field java.util.TimeZone.defaultTimeZone
WARNING: Please consider reporting this to the maintainers of org.postgresql.jdbc.TimestampUtils
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
```

I think it's self-explaining. Seems to be related [here](https://github.com/pgjdbc/pgjdbc/blob/1e5bf563f41203417281117ed20b183cd295b4e0/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java#L82).


Pull request description:
fix: avoid reflective access to TimeZone.defaultTimeZone in Java 9+
closes #986

Patch:
diff --git a/pgjdbc/src/main/java/org/postgresql/core/JavaVersion.java b/pgjdbc/src/main/java/org/postgresql/core/JavaVersion.java
new file mode 100644
index 00000000..82773b27
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/core/JavaVersion.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+public enum JavaVersion {
+  // Note: order is important,
+  v1_6,
+  v1_7,
+  v1_8,
+  other;
+
+  private static final JavaVersion RUNTIME_VERSION = from(System.getProperty("java.version"));
+
+  /**
+   * Returns enum value that represents current runtime. For instance, when using -jre7.jar via Java
+   * 8, this would return v18
+   *
+   * @return enum value that represents current runtime.
+   */
+  public static JavaVersion getRuntimeVersion() {
+    return RUNTIME_VERSION;
+  }
+
+  /**
+   * Java version string like in {@code "java.version"} property
+   *
+   * @param version string like 1.6, 1.7, etc
+   * @return JavaVersion enum
+   */
+  public static JavaVersion from(String version) {
+    // Minimum supported is Java 1.6
+    if (version.startsWith("1.6")) {
+      return v1_6;
+    }
+    if (version.startsWith("1.7")) {
+      return v1_7;
+    }
+    if (version.startsWith("1.8")) {
+      return v1_8;
+    }
+    return other;
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
index 74951366..aad8d137 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
@@ -6,6 +6,7 @@
 package org.postgresql.jdbc;
 
 import org.postgresql.PGStatement;
+import org.postgresql.core.JavaVersion;
 import org.postgresql.core.Oid;
 import org.postgresql.core.Provider;
 import org.postgresql.util.ByteConverter;
@@ -85,12 +86,16 @@ public class TimestampUtils {
     // This saves the creation of a clone everytime, and the memory associated to all these clones.
     Field tzField;
     try {
-      tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
-      tzField.setAccessible(true);
-      TimeZone defaultTz = TimeZone.getDefault();
-      Object tzFromField = tzField.get(null);
-      if (defaultTz == null || !defaultTz.equals(tzFromField)) {
-        tzField = null;
+      tzField = null;
+      // Avoid reflective access in Java 9+
+      if (JavaVersion.getRuntimeVersion().compareTo(JavaVersion.v1_8) <= 0) {
+        tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
+        tzField.setAccessible(true);
+        TimeZone defaultTz = TimeZone.getDefault();
+        Object tzFromField = tzField.get(null);
+        if (defaultTz == null || !defaultTz.equals(tzFromField)) {
+          tzField = null;
+        }
       }
     } catch (Exception e) {
       tzField = null;


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/test/core/JavaVersionTest.java b/pgjdbc/src/test/java/org/postgresql/test/core/JavaVersionTest.java
new file mode 100644
index 00000000..ebac017a
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/core/JavaVersionTest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.core;
+
+import org.postgresql.core.JavaVersion;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JavaVersionTest {
+  @Test
+  public void testGetRuntimeVersion() {
+    String currentVersion = System.getProperty("java.version");
+    String msg = "java.version = " + currentVersion + ", JavaVersion.getRuntimeVersion() = "
+        + JavaVersion.getRuntimeVersion();
+    System.out.println(msg);
+    if (currentVersion.startsWith("1.8")) {
+      Assert.assertEquals(msg, JavaVersion.v1_8, JavaVersion.getRuntimeVersion());
+    }
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
index 188183a1..87a04e3e 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
@@ -9,6 +9,7 @@ import org.postgresql.core.ParserTest;
 import org.postgresql.core.ReturningParserTest;
 import org.postgresql.core.v3.V3ParameterListTests;
 import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
+import org.postgresql.test.core.JavaVersionTest;
 import org.postgresql.test.core.NativeQueryBindLengthTest;
 import org.postgresql.test.util.ExpressionPropertiesTest;
 import org.postgresql.test.util.LruCacheTest;
@@ -25,6 +26,7 @@ import org.junit.runners.Suite;
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
         ANTTest.class,
+        JavaVersionTest.class,
 
         DriverTest.class,
         ConnectionTest.class,