8066985: Java Webstart downloading packed files can result in Timezone set to UTC

Reviewed-by: ksrini
diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/PackerImpl.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
index 309c68a..a90cbc3 100644
--- a/jdk/src/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
+++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/PackerImpl.java
@@ -79,18 +79,20 @@
      * Takes a JarFile and converts into a pack-stream.
      * <p>
      * Closes its input but not its output.  (Pack200 archives are appendable.)
-     * @param in a JarFile
+     *
+     * @param in  a JarFile
      * @param out an OutputStream
      * @exception IOException if an error is encountered.
      */
     public synchronized void pack(JarFile in, OutputStream out) throws IOException {
-        assert(Utils.currentInstance.get() == null);
-        TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE))
-                      ? null
-                      : TimeZone.getDefault();
+        assert (Utils.currentInstance.get() == null);
+
+        boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
         try {
             Utils.currentInstance.set(this);
-            if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            if (needUTC) {
+                Utils.changeDefaultTimeZoneToUtc();
+            }
 
             if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
                 Utils.copyJarFile(in, out);
@@ -99,7 +101,9 @@
             }
         } finally {
             Utils.currentInstance.set(null);
-            if (tz != null) TimeZone.setDefault(tz);
+            if (needUTC) {
+                Utils.restoreDefaultTimeZone();
+            }
             in.close();
         }
     }
@@ -119,12 +123,13 @@
      * @exception IOException if an error is encountered.
      */
     public synchronized void pack(JarInputStream in, OutputStream out) throws IOException {
-        assert(Utils.currentInstance.get() == null);
-        TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null :
-            TimeZone.getDefault();
+        assert (Utils.currentInstance.get() == null);
+        boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
         try {
             Utils.currentInstance.set(this);
-            if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            if (needUTC) {
+                Utils.changeDefaultTimeZoneToUtc();
+            }
             if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
                 Utils.copyJarFile(in, out);
             } else {
@@ -132,10 +137,13 @@
             }
         } finally {
             Utils.currentInstance.set(null);
-            if (tz != null) TimeZone.setDefault(tz);
+            if (needUTC) {
+                Utils.restoreDefaultTimeZone();
+            }
             in.close();
         }
     }
+
     /**
      * Register a listener for changes to options.
      * @param listener  An object to be invoked when a property is changed.
diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java
index 9044d92..7bc6281 100644
--- a/jdk/src/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java
+++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java
@@ -96,13 +96,15 @@
     //Driver routines
 
     // The unpack worker...
+
     /**
      * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally
      * the entire buffer must be read, it may be more efficient to read the packed-stream
      * to a file and pass the File object, in the alternate method described below.
      * <p>
      * Closes its input but not its output.  (The output can accumulate more elements.)
-     * @param in an InputStream.
+     *
+     * @param in  an InputStream.
      * @param out a JarOutputStream.
      * @exception IOException if an error is encountered.
      */
@@ -113,19 +115,19 @@
         if (out == null) {
             throw new NullPointerException("null output");
         }
-        assert(Utils.currentInstance.get() == null);
-        TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE))
-                      ? null
-                      : TimeZone.getDefault();
-
+        assert (Utils.currentInstance.get() == null);
+        boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
         try {
             Utils.currentInstance.set(this);
-            if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+            if (needUTC) {
+                Utils.changeDefaultTimeZoneToUtc();
+            }
             final int verbose = props.getInteger(Utils.DEBUG_VERBOSE);
             BufferedInputStream in0 = new BufferedInputStream(in);
             if (Utils.isJarMagic(Utils.readMagic(in0))) {
-                if (verbose > 0)
+                if (verbose > 0) {
                     Utils.log.info("Copying unpacked JAR file...");
+                }
                 Utils.copyJarFile(new JarInputStream(in0), out);
             } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) {
                 (new DoUnpack()).run(in0, out);
@@ -144,7 +146,9 @@
         } finally {
             _nunp = null;
             Utils.currentInstance.set(null);
-            if (tz != null) TimeZone.setDefault(tz);
+            if (needUTC) {
+                Utils.restoreDefaultTimeZone();
+            }
         }
     }
 
@@ -152,7 +156,8 @@
      * Takes an input File containing the pack file, and generates a JarOutputStream.
      * <p>
      * Does not close its output.  (The output can accumulate more elements.)
-     * @param in a File.
+     *
+     * @param in  a File.
      * @param out a JarOutputStream.
      * @exception IOException if an error is encountered.
      */
diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/Utils.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/Utils.java
index ebe0960..dea559d 100644
--- a/jdk/src/share/classes/com/sun/java/util/jar/pack/Utils.java
+++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/Utils.java
@@ -34,6 +34,7 @@
 import java.io.OutputStream;
 import java.util.Collections;
 import java.util.Date;
+import java.util.TimeZone;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.JarInputStream;
@@ -133,6 +134,9 @@
     // to the engine code, especially the native code.
     static final ThreadLocal<TLGlobals> currentInstance = new ThreadLocal<>();
 
+    private static TimeZone tz;
+    private static int workingPackerCount = 0;
+
     // convenience method to access the TL globals
     static TLGlobals getTLGlobals() {
         return currentInstance.get();
@@ -203,6 +207,24 @@
         }
     }
 
+    static synchronized void changeDefaultTimeZoneToUtc() {
+        if (workingPackerCount++ == 0) {
+            // only first thread saves default TZ
+            tz = TimeZone.getDefault();
+            TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        }
+    }
+
+    static synchronized void restoreDefaultTimeZone() {
+        if (--workingPackerCount == 0) {
+            // reset timezone when all the packer/unpacker instances have terminated
+            if (tz != null) {
+                TimeZone.setDefault(tz);
+            }
+            tz = null;
+        }
+    }
+
     static final Pack200Logger log
         = new Pack200Logger("java.util.jar.Pack200");
 
diff --git a/jdk/test/tools/pack200/DefaultTimeZoneTest.java b/jdk/test/tools/pack200/DefaultTimeZoneTest.java
new file mode 100644
index 0000000..0402957
--- /dev/null
+++ b/jdk/test/tools/pack200/DefaultTimeZoneTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+
+/*
+ * @test
+ * @bug 8066985
+ * @summary multithreading packing/unpacking files can result in Timezone set to UTC
+ * @compile -XDignore.symbol.file Utils.java DefaultTimeZoneTest.java
+ * @run main/othervm DefaultTimeZoneTest
+ * @author mcherkas
+ */
+
+
+public class DefaultTimeZoneTest {
+
+    private static final TimeZone tz = TimeZone.getTimeZone("Europe/Moscow");
+    private static final int INIT_THREAD_COUNT = Math.min(4, Runtime.getRuntime().availableProcessors());
+    private static final int MAX_THREAD_COUNT = 4 * INIT_THREAD_COUNT;
+    private static final long MINUTE = 60 * 1000;
+
+    private static class NoOpOutputStream extends OutputStream {
+        @Override
+        public void write(int b) throws IOException {
+            // no op
+        }
+    }
+
+    static class PackAction implements Runnable {
+        private Pack200.Packer packer = Pack200.newPacker();
+        private JarFile jarFile;
+
+        PackAction() throws IOException {
+            jarFile = new JarFile(new File("golden.jar"));
+        }
+
+        @Override
+        public void run() {
+            try {
+                packer.pack(jarFile, new NoOpOutputStream());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    static class UnpackAction implements Runnable {
+        private Pack200.Unpacker unpacker = Pack200.newUnpacker();
+        private JarOutputStream jos;
+        private File packedJar = new File("golden.pack");
+
+        UnpackAction() throws IOException {
+            jos = new JarOutputStream(new NoOpOutputStream());
+        }
+
+        @Override
+        public void run() {
+            try {
+                unpacker.unpack(packedJar, jos);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            } finally {
+                try {
+                    jos.close();
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    };
+
+    public static void test(final Class<? extends Runnable> runnableClass) throws InterruptedException {
+        for (int i = INIT_THREAD_COUNT; i <= MAX_THREAD_COUNT; i*=2) {
+            final CountDownLatch startLatch = new CountDownLatch(i);
+            final CountDownLatch doneLatch = new CountDownLatch(i);
+            for (int j = 0; j < i; j++) {
+                new Thread() {
+                    @Override
+                    public void run() {
+                        try {
+                            Runnable r = runnableClass.newInstance();
+                            startLatch.countDown();
+                            startLatch.await();
+                            r.run();
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        } finally {
+                            doneLatch.countDown();
+                        }
+                    }
+                }.start();
+            }
+            doneLatch.await();
+
+            if(!TimeZone.getDefault().equals(tz)) {
+                throw new RuntimeException("FAIL: default time zone was changed");
+            }
+        }
+    }
+
+    public static void main(String args[]) throws IOException, InterruptedException {
+        TimeZone.setDefault(tz);
+
+        // make a local copy of our test file
+        File srcFile = Utils.locateJar("golden.jar");
+        final File goldenFile = new File("golden.jar");
+        Utils.copyFile(srcFile, goldenFile);
+
+        // created packed file
+        final JarFile goldenJarFile = new JarFile(goldenFile);
+        final File packFile = new File("golden.pack");
+        Utils.pack(goldenJarFile, packFile);
+
+        // before test let's unpack golden pack to warm up
+        // a native unpacker. That allow us to avoid JDK-8080438
+        UnpackAction unpackAction = new UnpackAction();
+        unpackAction.run();
+
+        long startTime = System.currentTimeMillis();
+        while(System.currentTimeMillis() - startTime < MINUTE) {
+            // test packer
+            test(PackAction.class);
+
+            // test unpacker
+            test(UnpackAction.class);
+        }
+
+        Utils.cleanup();
+    }
+}