From 784f62993b0d54abbf2a228d71ad89840208f85e Mon Sep 17 00:00:00 2001 From: Chris Hegarty Date: Tue, 3 Aug 2010 12:03:03 +0100 Subject: [PATCH 01/32] 6973030: NTLM proxy authentication fails with https Reviewed-by: michaelm --- .../www/protocol/http/HttpURLConnection.java | 4 + .../https/HttpsURLConnection/B6226610.java | 79 ++++++------------- 2 files changed, 30 insertions(+), 53 deletions(-) diff --git a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 713c7d85c21..e888abe26e8 100644 --- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -1768,6 +1768,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection { // Not really necessary for a tunnel, but can't hurt requests.setIfNotSet("Accept", acceptString); + if (http.getHttpKeepAliveSet()) { + requests.setIfNotSet("Proxy-Connection", "keep-alive"); + } + setPreemptiveProxyAuthentication(requests); /* Log the CONNECT request */ diff --git a/jdk/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/B6226610.java b/jdk/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/B6226610.java index 3856ef7ebe1..7481a6cbf04 100644 --- a/jdk/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/B6226610.java +++ b/jdk/test/sun/security/ssl/sun/net/www/protocol/https/HttpsURLConnection/B6226610.java @@ -23,7 +23,7 @@ /* * @test - * @bug 6226610 + * @bug 6226610 6973030 * @run main/othervm B6226610 * @summary HTTP tunnel connections send user headers to proxy */ @@ -36,45 +36,23 @@ import java.io.*; import java.net.*; -import javax.net.ssl.*; -import javax.net.ServerSocketFactory; -import sun.net.www.*; -import java.util.Enumeration; +import sun.net.www.MessageHeader; public class B6226610 { static HeaderCheckerProxyTunnelServer proxy; - // it seems there's no proxy ever if a url points to 'localhost', - // even if proxy related properties are set. so we need to bind - // our simple http proxy and http server to a non-loopback address - static InetAddress firstNonLoAddress = null; - - public static void main(String[] args) + public static void main(String[] args) throws Exception { - try { - proxy = new HeaderCheckerProxyTunnelServer(); - proxy.start(); - } catch (Exception e) { - System.out.println("Cannot create proxy: " + e); - } + proxy = new HeaderCheckerProxyTunnelServer(); + proxy.start(); - try { - firstNonLoAddress = getNonLoAddress(); - - if (firstNonLoAddress == null) { - System.out.println("The test needs at least one non-loopback address to run. Quit now."); - System.exit(0); - } - } catch (Exception e) { - e.printStackTrace(); - } - - System.setProperty( "https.proxyHost", firstNonLoAddress.getHostAddress()); - System.setProperty( "https.proxyPort", (new Integer(proxy.getLocalPort())).toString() ); + String hostname = InetAddress.getLocalHost().getHostName(); try { - URL u = new URL("https://" + firstNonLoAddress.getHostAddress()); - java.net.URLConnection c = u.openConnection(); + URL u = new URL("https://" + hostname + "/"); + System.out.println("Connecting to " + u); + InetSocketAddress proxyAddr = new InetSocketAddress(hostname, proxy.getLocalPort()); + java.net.URLConnection c = u.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddr)); /* I want this header to go to the destination server only, protected * by SSL @@ -89,33 +67,15 @@ public class B6226610 { } else System.out.println(e); - + } finally { + if (proxy != null) proxy.shutdown(); } if (HeaderCheckerProxyTunnelServer.failed) - throw new RuntimeException("Test failed: Proxy should not receive user defined headers for tunneled requests"); + throw new RuntimeException("Test failed; see output"); } - - public static InetAddress getNonLoAddress() throws Exception { - NetworkInterface loNIC = NetworkInterface.getByInetAddress(InetAddress.getByName("localhost")); - Enumeration nics = NetworkInterface.getNetworkInterfaces(); - while (nics.hasMoreElements()) { - NetworkInterface nic = nics.nextElement(); - if (!nic.getName().equalsIgnoreCase(loNIC.getName())) { - Enumeration addrs = nic.getInetAddresses(); - while (addrs.hasMoreElements()) { - InetAddress addr = addrs.nextElement(); - if (!addr.isLoopbackAddress()) - return addr; - } - } - } - return null; - } - } - class HeaderCheckerProxyTunnelServer extends Thread { public static boolean failed = false; @@ -139,6 +99,10 @@ class HeaderCheckerProxyTunnelServer extends Thread } } + void shutdown() { + try { ss.close(); } catch (IOException e) {} + } + public void run() { try { @@ -178,6 +142,15 @@ class HeaderCheckerProxyTunnelServer extends Thread retrieveConnectInfo(statusLine); if (mheader.findValue("X-TestHeader") != null) { + System.out.println("Proxy should not receive user defined headers for tunneled requests"); + failed = true; + } + + // 6973030 + String value; + if ((value = mheader.findValue("Proxy-Connection")) == null || + !value.equals("keep-alive")) { + System.out.println("Proxy-Connection:keep-alive not being sent"); failed = true; } From 707346f0994cc94ec8c3b4f8008adee2fc84b613 Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Tue, 3 Aug 2010 09:39:52 -0400 Subject: [PATCH 02/32] 6653372: Error in java.security.KeyStore example code Reviewed-by: weijun --- jdk/src/share/classes/java/security/KeyStore.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jdk/src/share/classes/java/security/KeyStore.java b/jdk/src/share/classes/java/security/KeyStore.java index 7ecac8025c1..c1e2e4649e7 100644 --- a/jdk/src/share/classes/java/security/KeyStore.java +++ b/jdk/src/share/classes/java/security/KeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2010, 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 @@ -131,17 +131,19 @@ import javax.security.auth.callback.*; * to read existing entries from the keystore, or to write new entries * into the keystore: *
+ *    KeyStore.ProtectionParameter protParam =
+ *        new KeyStore.PasswordProtection(password);
+ *
  *    // get my private key
  *    KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
- *        ks.getEntry("privateKeyAlias", password);
+ *        ks.getEntry("privateKeyAlias", protParam);
  *    PrivateKey myPrivateKey = pkEntry.getPrivateKey();
  *
  *    // save my secret key
  *    javax.crypto.SecretKey mySecretKey;
  *    KeyStore.SecretKeyEntry skEntry =
  *        new KeyStore.SecretKeyEntry(mySecretKey);
- *    ks.setEntry("secretKeyAlias", skEntry,
- *        new KeyStore.PasswordProtection(password));
+ *    ks.setEntry("secretKeyAlias", skEntry, protParam);
  *
  *    // store away the keystore
  *    java.io.FileOutputStream fos = null;

From 500a66b0d8a31f482597b26ca1e41aeb66738322 Mon Sep 17 00:00:00 2001
From: Martin Buchholz 
Date: Tue, 3 Aug 2010 12:22:49 -0700
Subject: [PATCH 03/32] 6955504: (str)
 String[Builder/Buffer].append(char[],int,int) throws OutOfMemoryError in b94

Let arraycopy throw AIOOBE for invalid negative length

Reviewed-by: chegar, forax
---
 jdk/src/share/classes/java/lang/AbstractStringBuilder.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/jdk/src/share/classes/java/lang/AbstractStringBuilder.java b/jdk/src/share/classes/java/lang/AbstractStringBuilder.java
index fad8ad6da66..46a14a0aa20 100644
--- a/jdk/src/share/classes/java/lang/AbstractStringBuilder.java
+++ b/jdk/src/share/classes/java/lang/AbstractStringBuilder.java
@@ -470,7 +470,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
     public AbstractStringBuilder append(CharSequence s, int start, int end) {
         if (s == null)
             s = "null";
-        if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
+        if ((start < 0) || (start > end) || (end > s.length()))
             throw new IndexOutOfBoundsException(
                 "start " + start + ", end " + end + ", s.length() "
                 + s.length());
@@ -529,7 +529,8 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
      *         or {@code offset+len > str.length}
      */
     public AbstractStringBuilder append(char str[], int offset, int len) {
-        ensureCapacityInternal(count + len);
+        if (len > 0)                // let arraycopy report AIOOBE for len < 0
+            ensureCapacityInternal(count + len);
         System.arraycopy(str, offset, value, count, len);
         count += len;
         return this;

From bc38b1c703b1d89b77a14020cdaf85908d94d1b7 Mon Sep 17 00:00:00 2001
From: Andrei Pangin 
Date: Wed, 4 Aug 2010 20:25:02 -0700
Subject: [PATCH 04/32] 6945961: SIGSEGV in memcpy() during class loading on
 linux-i586

Check the result of strchr() in Bytecode Verifier

Reviewed-by: kamg, acorn
---
 jdk/src/share/native/common/check_code.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/jdk/src/share/native/common/check_code.c b/jdk/src/share/native/common/check_code.c
index 337a11f4ca4..0a2cb1433b4 100644
--- a/jdk/src/share/native/common/check_code.c
+++ b/jdk/src/share/native/common/check_code.c
@@ -2730,7 +2730,10 @@ push_stack(context_type *context, unsigned int inumber, stack_info_type *new_sta
                                                                 operand);
             const char *result_signature;
             check_and_push(context, signature, VM_STRING_UTF);
-            result_signature = strchr(signature, JVM_SIGNATURE_ENDFUNC) + 1;
+            result_signature = strchr(signature, JVM_SIGNATURE_ENDFUNC);
+            if (result_signature++ == NULL) {
+                CCerror(context, "Illegal signature %s", signature);
+            }
             if (result_signature[0] == JVM_SIGNATURE_VOID) {
                 stack_results = "";
             } else {
@@ -3654,14 +3657,13 @@ signature_to_fieldtype(context_type *context,
                        const char **signature_p, fullinfo_type *full_info_p)
 {
     const char *p = *signature_p;
-    fullinfo_type full_info = MAKE_FULLINFO(0, 0, 0);
+    fullinfo_type full_info = MAKE_FULLINFO(ITEM_Bogus, 0, 0);
     char result;
     int array_depth = 0;
 
     for (;;) {
         switch(*p++) {
             default:
-                full_info = MAKE_FULLINFO(ITEM_Bogus, 0, 0);
                 result = 0;
                 break;
 
@@ -3714,7 +3716,14 @@ signature_to_fieldtype(context_type *context,
                 char buffer_space[256];
                 char *buffer = buffer_space;
                 char *finish = strchr(p, JVM_SIGNATURE_ENDCLASS);
-                int length = finish - p;
+                int length;
+                if (finish == NULL) {
+                    /* Signature must have ';' after the class name.
+                     * If it does not, return 0 and ITEM_Bogus in full_info. */
+                    result = 0;
+                    break;
+                }
+                length = finish - p;
                 if (length + 1 > (int)sizeof(buffer_space)) {
                     buffer = malloc(length + 1);
                     check_and_push(context, buffer, VM_MALLOC_BLK);

From 438ccadcce697c4189638a282a490b1290a59ac3 Mon Sep 17 00:00:00 2001
From: "Daniel D. Daugherty" 
Date: Fri, 6 Aug 2010 11:07:16 -0700
Subject: [PATCH 05/32] 6962604: 3/3 Testcase
 sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh failure

Disable MonitorVmStartTerminate.sh until 6543856 is fixed.

Reviewed-by: ohair
---
 .../sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh b/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh
index 917f1c22833..49d5f44be71 100644
--- a/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh
+++ b/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh
@@ -23,6 +23,7 @@
 
 #
 # @test
+# @ignore until 6543856 is fixed
 # @bug 4990825
 # @summary attach to external but local JVM processes
 # @library ../../testlibrary

From 1716bf859b7c0630a19e4b81b500dbf7395a97a9 Mon Sep 17 00:00:00 2001
From: Lance Andersen 
Date: Tue, 10 Aug 2010 10:07:33 -0400
Subject: [PATCH 06/32] 6898593: java.sql.Date.valueOf no exception if date
 given is not in the JDBC date escape syntax

Reviewed-by: minqi
---
 jdk/src/share/classes/java/sql/Date.java | 45 +++++++++++++++++-------
 1 file changed, 32 insertions(+), 13 deletions(-)

diff --git a/jdk/src/share/classes/java/sql/Date.java b/jdk/src/share/classes/java/sql/Date.java
index 39cf98ce93f..7c2356e75c2 100644
--- a/jdk/src/share/classes/java/sql/Date.java
+++ b/jdk/src/share/classes/java/sql/Date.java
@@ -103,27 +103,46 @@ public class Date extends java.util.Date {
      *         JDBC date escape format (yyyy-mm-dd)
      */
     public static Date valueOf(String s) {
-        int year;
-        int month;
-        int day;
+        final int YEAR_LENGTH = 4;
+        final int MONTH_LENGTH = 2;
+        final int DAY_LENGTH = 2;
+        final int MAX_MONTH = 12;
+        final int MAX_DAY = 31;
         int firstDash;
         int secondDash;
+        Date d = null;
 
-        if (s == null) throw new java.lang.IllegalArgumentException();
-
-        firstDash = s.indexOf('-');
-        secondDash = s.indexOf('-', firstDash+1);
-        if ((firstDash > 0) & (secondDash > 0) & (secondDash < s.length()-1)) {
-            year = Integer.parseInt(s.substring(0, firstDash)) - 1900;
-            month = Integer.parseInt(s.substring(firstDash+1, secondDash)) - 1;
-            day = Integer.parseInt(s.substring(secondDash+1));
-        } else {
+        if (s == null) {
             throw new java.lang.IllegalArgumentException();
         }
 
-        return new Date(year, month, day);
+        firstDash = s.indexOf('-');
+        secondDash = s.indexOf('-', firstDash + 1);
+
+        if ((firstDash > 0) && (secondDash > 0) && (secondDash < s.length() - 1)) {
+            String yyyy = s.substring(0, firstDash);
+            String mm = s.substring(firstDash + 1, secondDash);
+            String dd = s.substring(secondDash + 1);
+            if (yyyy.length() == YEAR_LENGTH && mm.length() == MONTH_LENGTH &&
+                    dd.length() == DAY_LENGTH) {
+                int year = Integer.parseInt(yyyy);
+                int month = Integer.parseInt(mm);
+                int day = Integer.parseInt(dd);
+
+                if ((month >= 1 && month <= MAX_MONTH) && (day >= 1 && day <= MAX_DAY)) {
+                    d = new Date(year - 1900, month - 1, day);
+                }
+            }
+        }
+        if (d == null) {
+            throw new java.lang.IllegalArgumentException();
+        }
+
+        return d;
+
     }
 
+
     /**
      * Formats a date in the date escape format yyyy-mm-dd.
      * 

From e1edb38d9ed7df1d57f8e2f84ce972293640281f Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Tue, 10 Aug 2010 19:29:30 +0400 Subject: [PATCH 07/32] 6960267: JTable.getRowHeight() returns value different from the specified default (16.0) with GTK L&F Reviewed-by: peterz --- jdk/src/share/classes/javax/swing/JTable.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdk/src/share/classes/javax/swing/JTable.java b/jdk/src/share/classes/javax/swing/JTable.java index 8ca98bbe15a..bcf00a37547 100644 --- a/jdk/src/share/classes/javax/swing/JTable.java +++ b/jdk/src/share/classes/javax/swing/JTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2010, 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 @@ -946,7 +946,6 @@ public class JTable extends JComponent implements TableModelListener, Scrollable /** * Returns the height of a table row, in pixels. - * The default row height is 16.0. * * @return the height in pixels of a table row * @see #setRowHeight From 22534d46e9fecc59de8cf18fd3e1bbfcba191e4a Mon Sep 17 00:00:00 2001 From: Chris Hegarty Date: Tue, 10 Aug 2010 17:30:43 +0100 Subject: [PATCH 08/32] 6882910: Unexplained lack of IP4 network ability when transparent IP6 to IP4 is disabled Reviewed-by: alanb --- .../native/java/net/PlainDatagramSocketImpl.c | 36 +++++++++------- .../solaris/native/java/net/PlainSocketImpl.c | 42 +++++++++++++------ jdk/src/solaris/native/sun/nio/ch/Net.c | 16 +++++++ 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/jdk/src/solaris/native/java/net/PlainDatagramSocketImpl.c b/jdk/src/solaris/native/java/net/PlainDatagramSocketImpl.c index e717322c6ea..48d0d8ecfa8 100644 --- a/jdk/src/solaris/native/java/net/PlainDatagramSocketImpl.c +++ b/jdk/src/solaris/native/java/net/PlainDatagramSocketImpl.c @@ -1052,30 +1052,38 @@ JNIEXPORT void JNICALL Java_java_net_PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env, jobject this) { jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID); - int fd; - - int t = 1; + int fd, t = 1; +#ifdef AF_INET6 + int domain = ipv6_available() ? AF_INET6 : AF_INET; +#else + int domain = AF_INET; +#endif if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; - } else { -#ifdef AF_INET6 - if (ipv6_available()) { - fd = JVM_Socket(AF_INET6, SOCK_DGRAM, 0); - } else -#endif /* AF_INET6 */ - { - fd = JVM_Socket(AF_INET, SOCK_DGRAM, 0); - } } - if (fd == JVM_IO_ERR) { + + if ((fd = JVM_Socket(domain, SOCK_DGRAM, 0)) == JVM_IO_ERR) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error creating socket"); return; } +#ifdef AF_INET6 + /* Disable IPV6_V6ONLY to ensure dual-socket support */ + if (domain == AF_INET6) { + int arg = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, + sizeof(int)) < 0) { + NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6"); + close(fd); + return; + } + } +#endif /* AF_INET6 */ + setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof(int)); #ifdef __linux__ @@ -1088,7 +1096,7 @@ Java_java_net_PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env, * On Linux for IPv6 sockets we must set the hop limit * to 1 to be compatible with default ttl of 1 for IPv4 sockets. */ - if (ipv6_available()) { + if (domain == AF_INET6) { int ttl = 1; setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof(ttl)); diff --git a/jdk/src/solaris/native/java/net/PlainSocketImpl.c b/jdk/src/solaris/native/java/net/PlainSocketImpl.c index 254a1497ff6..060dacdac86 100644 --- a/jdk/src/solaris/native/java/net/PlainSocketImpl.c +++ b/jdk/src/solaris/native/java/net/PlainSocketImpl.c @@ -181,6 +181,12 @@ Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this, jboolean stream) { jobject fdObj, ssObj; int fd; + int type = (stream ? SOCK_STREAM : SOCK_DGRAM); +#ifdef AF_INET6 + int domain = ipv6_available() ? AF_INET6 : AF_INET; +#else + int domain = AF_INET; +#endif if (socketExceptionCls == NULL) { jclass c = (*env)->FindClass(env, "java/net/SocketException"); @@ -194,25 +200,29 @@ Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this, (*env)->ThrowNew(env, socketExceptionCls, "null fd object"); return; } -#ifdef AF_INET6 - if (ipv6_available()) { - fd = JVM_Socket(AF_INET6, (stream ? SOCK_STREAM: SOCK_DGRAM), 0); - } else -#endif /* AF_INET6 */ - { - fd = JVM_Socket(AF_INET, (stream ? SOCK_STREAM: SOCK_DGRAM), 0); - } - if (fd == JVM_IO_ERR) { + + if ((fd = JVM_Socket(domain, type, 0)) == JVM_IO_ERR) { /* note: if you run out of fds, you may not be able to load * the exception class, and get a NoClassDefFoundError * instead. */ NET_ThrowNew(env, errno, "can't create socket"); return; - } else { - (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); } +#ifdef AF_INET6 + /* Disable IPV6_V6ONLY to ensure dual-socket support */ + if (domain == AF_INET6) { + int arg = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, + sizeof(int)) < 0) { + NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6"); + close(fd); + return; + } + } +#endif /* AF_INET6 */ + /* * If this is a server socket then enable SO_REUSEADDR * automatically and set to non blocking. @@ -221,9 +231,15 @@ Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this, if (ssObj != NULL) { int arg = 1; SET_NONBLOCKING(fd); - JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, - sizeof(arg)); + if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, + sizeof(arg)) < 0) { + NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR"); + close(fd); + return; + } } + + (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); } /* diff --git a/jdk/src/solaris/native/sun/nio/ch/Net.c b/jdk/src/solaris/native/sun/nio/ch/Net.c index e0b3d1c1152..dc3d7c4ac34 100644 --- a/jdk/src/solaris/native/sun/nio/ch/Net.c +++ b/jdk/src/solaris/native/sun/nio/ch/Net.c @@ -170,6 +170,22 @@ Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, if (fd < 0) { return handleSocketError(env, errno); } + +#ifdef AF_INET6 + /* Disable IPV6_V6ONLY to ensure dual-socket support */ + if (domain == AF_INET6) { + int arg = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, + sizeof(int)) < 0) { + JNU_ThrowByNameWithLastError(env, + JNU_JAVANETPKG "SocketException", + "sun.nio.ch.Net.setIntOption"); + close(fd); + return -1; + } + } +#endif + if (reuse) { int arg = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, From 55323320b6fe2cda2a04a4a88b8f74dbc2742500 Mon Sep 17 00:00:00 2001 From: Denis Lila Date: Tue, 10 Aug 2010 13:19:44 -0400 Subject: [PATCH 09/32] 6967436: lines longer than 2^15 can fill window 6967433: dashed lines broken when using scaling transforms Converted pisces to floating point. Also, using better AA algorithm Reviewed-by: flar --- .../classes/sun/java2d/pisces/Dasher.java | 199 ++- .../classes/sun/java2d/pisces/LineSink.java | 20 +- .../classes/sun/java2d/pisces/PiscesMath.java | 155 --- .../java2d/pisces/PiscesRenderingEngine.java | 89 +- .../classes/sun/java2d/pisces/Renderer.java | 1109 ++++++----------- .../classes/sun/java2d/pisces/Stroker.java | 393 +++--- .../classes/sun/java2d/pisces/Transform4.java | 84 -- 7 files changed, 662 insertions(+), 1387 deletions(-) delete mode 100644 jdk/src/share/classes/sun/java2d/pisces/PiscesMath.java delete mode 100644 jdk/src/share/classes/sun/java2d/pisces/Transform4.java diff --git a/jdk/src/share/classes/sun/java2d/pisces/Dasher.java b/jdk/src/share/classes/sun/java2d/pisces/Dasher.java index 1a1f328adc4..f5b5e049143 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/Dasher.java +++ b/jdk/src/share/classes/sun/java2d/pisces/Dasher.java @@ -36,117 +36,71 @@ package sun.java2d.pisces; * semantics are unclear. * */ -public class Dasher extends LineSink { +public class Dasher implements LineSink { + private final LineSink output; + private final float[] dash; + private final float startPhase; + private final boolean startDashOn; + private final int startIdx; - LineSink output; - int[] dash; - int startPhase; - boolean startDashOn; - int startIdx; + private final float m00, m10, m01, m11; + private final float det; - int idx; - boolean dashOn; - int phase; + private boolean firstDashOn; + private boolean starting; - int sx, sy; - int x0, y0; + private int idx; + private boolean dashOn; + private float phase; - int m00, m01; - int m10, m11; + private float sx, sy; + private float x0, y0; + private float sx1, sy1; - Transform4 transform; - - boolean symmetric; - long ldet; - - boolean firstDashOn; - boolean starting; - int sx1, sy1; - - /** - * Empty constructor. setOutput and - * setParameters must be called prior to calling any - * other methods. - */ - public Dasher() {} /** * Constructs a Dasher. * * @param output an output LineSink. - * @param dash an array of ints containing the dash - * pattern in S15.16 format. - * @param phase an int containing the dash phase in - * S15.16 format. + * @param dash an array of ints containing the dash pattern + * @param phase an int containing the dash phase * @param transform a Transform4 object indicating * the transform that has been previously applied to all incoming * coordinates. This is required in order to compute dash lengths * properly. */ public Dasher(LineSink output, - int[] dash, int phase, - Transform4 transform) { - setOutput(output); - setParameters(dash, phase, transform); - } - - /** - * Sets the output LineSink of this - * Dasher. - * - * @param output an output LineSink. - */ - public void setOutput(LineSink output) { - this.output = output; - } - - /** - * Sets the parameters of this Dasher. - * - * @param dash an array of ints containing the dash - * pattern in S15.16 format. - * @param phase an int containing the dash phase in - * S15.16 format. - * @param transform a Transform4 object indicating - * the transform that has been previously applied to all incoming - * coordinates. This is required in order to compute dash lengths - * properly. - */ - public void setParameters(int[] dash, int phase, - Transform4 transform) { + float[] dash, float phase, + float a00, float a01, float a10, float a11) { if (phase < 0) { throw new IllegalArgumentException("phase < 0 !"); } + this.output = output; + // Normalize so 0 <= phase < dash[0] int idx = 0; dashOn = true; - int d; + float d; while (phase >= (d = dash[idx])) { phase -= d; idx = (idx + 1) % dash.length; dashOn = !dashOn; } - this.dash = new int[dash.length]; - for (int i = 0; i < dash.length; i++) { - this.dash[i] = dash[i]; - } + this.dash = dash; this.startPhase = this.phase = phase; this.startDashOn = dashOn; this.startIdx = idx; - this.transform = transform; - - this.m00 = transform.m00; - this.m01 = transform.m01; - this.m10 = transform.m10; - this.m11 = transform.m11; - this.ldet = ((long)m00*m11 - (long)m01*m10) >> 16; - this.symmetric = (m00 == m11 && m10 == -m01); + m00 = a00; + m01 = a01; + m10 = a10; + m11 = a11; + det = m00 * m11 - m01 * m10; } - public void moveTo(int x0, int y0) { + public void moveTo(float x0, float y0) { output.moveTo(x0, y0); this.idx = startIdx; this.dashOn = this.startDashOn; @@ -160,7 +114,7 @@ public class Dasher extends LineSink { output.lineJoin(); } - private void goTo(int x1, int y1) { + private void goTo(float x1, float y1) { if (dashOn) { if (starting) { this.sx1 = x1; @@ -180,52 +134,64 @@ public class Dasher extends LineSink { this.y0 = y1; } - public void lineTo(int x1, int y1) { + public void lineTo(float x1, float y1) { + // The widened line is squished to a 0 width one, so no drawing is done + if (det == 0) { + goTo(x1, y1); + return; + } + float dx = x1 - x0; + float dy = y1 - y0; + + + // Compute segment length in the untransformed + // coordinate system + + float la = (dy*m00 - dx*m10)/det; + float lb = (dy*m01 - dx*m11)/det; + float origLen = (float) Math.hypot(la, lb); + + if (origLen == 0) { + // Let the output LineSink deal with cases where dx, dy are 0. + goTo(x1, y1); + return; + } + + // The scaling factors needed to get the dx and dy of the + // transformed dash segments. + float cx = dx / origLen; + float cy = dy / origLen; + while (true) { - int d = dash[idx] - phase; - int lx = x1 - x0; - int ly = y1 - y0; - - // Compute segment length in the untransformed - // coordinate system - // IMPL NOTE - use fixed point - - int l; - if (symmetric) { - l = (int)((PiscesMath.hypot(lx, ly)*65536L)/ldet); - } else{ - long la = ((long)ly*m00 - (long)lx*m10)/ldet; - long lb = ((long)ly*m01 - (long)lx*m11)/ldet; - l = (int)PiscesMath.hypot(la, lb); - } - - if (l < d) { + float leftInThisDashSegment = dash[idx] - phase; + if (origLen < leftInThisDashSegment) { goTo(x1, y1); // Advance phase within current dash segment - phase += l; + phase += origLen; + return; + } else if (origLen == leftInThisDashSegment) { + goTo(x1, y1); + phase = 0f; + idx = (idx + 1) % dash.length; + dashOn = !dashOn; return; } - long t; - int xsplit, ysplit; -// // For zero length dashses, SE appears to move 1/8 unit -// // in device space -// if (d == 0) { -// double dlx = lx/65536.0; -// double dly = ly/65536.0; -// len = PiscesMath.hypot(dlx, dly); -// double dt = 1.0/(8*len); -// double dxsplit = (x0/65536.0) + dt*dlx; -// double dysplit = (y0/65536.0) + dt*dly; -// xsplit = (int)(dxsplit*65536.0); -// ysplit = (int)(dysplit*65536.0); -// } else { - t = ((long)d << 16)/l; - xsplit = x0 + (int)(t*(x1 - x0) >> 16); - ysplit = y0 + (int)(t*(y1 - y0) >> 16); -// } - goTo(xsplit, ysplit); + float dashx, dashy; + float dashdx = dash[idx] * cx; + float dashdy = dash[idx] * cy; + if (phase == 0) { + dashx = x0 + dashdx; + dashy = y0 + dashdy; + } else { + float p = (leftInThisDashSegment) / dash[idx]; + dashx = x0 + p * dashdx; + dashy = y0 + p * dashdy; + } + goTo(dashx, dashy); + + origLen -= (dash[idx] - phase); // Advance to next dash segment idx = (idx + 1) % dash.length; dashOn = !dashOn; @@ -233,6 +199,7 @@ public class Dasher extends LineSink { } } + public void close() { lineTo(sx, sy); if (firstDashOn) { diff --git a/jdk/src/share/classes/sun/java2d/pisces/LineSink.java b/jdk/src/share/classes/sun/java2d/pisces/LineSink.java index 6f92dd4a416..81300a25fa0 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/LineSink.java +++ b/jdk/src/share/classes/sun/java2d/pisces/LineSink.java @@ -39,16 +39,16 @@ package sun.java2d.pisces; * LineSink interface. * */ -public abstract class LineSink { +public interface LineSink { /** * Moves the current drawing position to the point (x0, * y0). * - * @param x0 the X coordinate in S15.16 format - * @param y0 the Y coordinate in S15.16 format + * @param x0 the X coordinate + * @param y0 the Y coordinate */ - public abstract void moveTo(int x0, int y0); + public void moveTo(float x0, float y0); /** * Provides a hint that the current segment should be joined to @@ -65,29 +65,29 @@ public abstract class LineSink { *

Other LineSink classes should simply pass this * hint to their output sink as needed. */ - public abstract void lineJoin(); + public void lineJoin(); /** * Draws a line from the current drawing position to the point * (x1, y1) and sets the current drawing position to * (x1, y1). * - * @param x1 the X coordinate in S15.16 format - * @param y1 the Y coordinate in S15.16 format + * @param x1 the X coordinate + * @param y1 the Y coordinate */ - public abstract void lineTo(int x1, int y1); + public void lineTo(float x1, float y1); /** * Closes the current path by drawing a line from the current * drawing position to the point specified by the moset recent * moveTo command. */ - public abstract void close(); + public void close(); /** * Ends the current path. It may be necessary to end a path in * order to allow end caps to be drawn. */ - public abstract void end(); + public void end(); } diff --git a/jdk/src/share/classes/sun/java2d/pisces/PiscesMath.java b/jdk/src/share/classes/sun/java2d/pisces/PiscesMath.java deleted file mode 100644 index 0a6e9421687..00000000000 --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesMath.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2007, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -package sun.java2d.pisces; - -public class PiscesMath { - - private PiscesMath() {} - - private static final int SINTAB_LG_ENTRIES = 10; - private static final int SINTAB_ENTRIES = 1 << SINTAB_LG_ENTRIES; - private static int[] sintab; - - public static final int PI = (int)(Math.PI*65536.0); - public static final int TWO_PI = (int)(2.0*Math.PI*65536.0); - public static final int PI_OVER_TWO = (int)((Math.PI/2.0)*65536.0); - public static final int SQRT_TWO = (int)(Math.sqrt(2.0)*65536.0); - - static { - sintab = new int[SINTAB_ENTRIES + 1]; - for (int i = 0; i < SINTAB_ENTRIES + 1; i++) { - double theta = i*(Math.PI/2.0)/SINTAB_ENTRIES; - sintab[i] = (int)(Math.sin(theta)*65536.0); - } - } - - public static int sin(int theta) { - int sign = 1; - if (theta < 0) { - theta = -theta; - sign = -1; - } - // 0 <= theta - while (theta >= TWO_PI) { - theta -= TWO_PI; - } - // 0 <= theta < 2*PI - if (theta >= PI) { - theta = TWO_PI - theta; - sign = -sign; - } - // 0 <= theta < PI - if (theta > PI_OVER_TWO) { - theta = PI - theta; - } - // 0 <= theta <= PI/2 - int itheta = (int)((long)theta*SINTAB_ENTRIES/(PI_OVER_TWO)); - return sign*sintab[itheta]; - } - - public static int cos(int theta) { - return sin(PI_OVER_TWO - theta); - } - -// public static double sqrt(double x) { -// double dsqrt = Math.sqrt(x); -// int ix = (int)(x*65536.0); -// Int Isqrt = Isqrt(Ix); - -// Long Lx = (Long)(X*65536.0); -// Long Lsqrt = Lsqrt(Lx); - -// System.Out.Println(); -// System.Out.Println("X = " + X); -// System.Out.Println("Dsqrt = " + Dsqrt); - -// System.Out.Println("Ix = " + Ix); -// System.Out.Println("Isqrt = " + Isqrt/65536.0); - -// System.Out.Println("Lx = " + Lx); -// System.Out.Println("Lsqrt = " + Lsqrt/65536.0); - -// Return Dsqrt; -// } - - // From Ken Turkowski, _Fixed-Point Square Root_, In Graphics Gems V - public static int isqrt(int x) { - int fracbits = 16; - - int root = 0; - int remHi = 0; - int remLo = x; - int count = 15 + fracbits/2; - - do { - remHi = (remHi << 2) | (remLo >>> 30); // N.B. - unsigned shift R - remLo <<= 2; - root <<= 1; - int testdiv = (root << 1) + 1; - if (remHi >= testdiv) { - remHi -= testdiv; - root++; - } - } while (count-- != 0); - - return root; - } - - public static long lsqrt(long x) { - int fracbits = 16; - - long root = 0; - long remHi = 0; - long remLo = x; - int count = 31 + fracbits/2; - - do { - remHi = (remHi << 2) | (remLo >>> 62); // N.B. - unsigned shift R - remLo <<= 2; - root <<= 1; - long testDiv = (root << 1) + 1; - if (remHi >= testDiv) { - remHi -= testDiv; - root++; - } - } while (count-- != 0); - - return root; - } - - public static double hypot(double x, double y) { - // new RuntimeException().printStackTrace(); - return Math.sqrt(x*x + y*y); - } - - public static int hypot(int x, int y) { - return (int)((lsqrt((long)x*x + (long)y*y) + 128) >> 8); - } - - public static long hypot(long x, long y) { - return (lsqrt(x*x + y*y) + 128) >> 8; - } -} diff --git a/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java b/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java index 6446cb109e3..23ef19ee31f 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java +++ b/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java @@ -37,24 +37,8 @@ import sun.java2d.pipe.RenderingEngine; import sun.java2d.pipe.AATileGenerator; public class PiscesRenderingEngine extends RenderingEngine { - public static Transform4 IdentT4 = new Transform4(); public static double defaultFlat = 0.1; - static int FloatToS15_16(float flt) { - flt = flt * 65536f + 0.5f; - if (flt <= -(65536f * 65536f)) { - return Integer.MIN_VALUE; - } else if (flt >= (65536f * 65536f)) { - return Integer.MAX_VALUE; - } else { - return (int) Math.floor(flt); - } - } - - static float S15_16ToFloat(int fix) { - return (fix / 65536f); - } - /** * Create a widened path as specified by the parameters. *

@@ -85,18 +69,19 @@ public class PiscesRenderingEngine extends RenderingEngine { strokeTo(src, null, width, + false, caps, join, miterlimit, dashes, dashphase, new LineSink() { - public void moveTo(int x0, int y0) { - p2d.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0)); + public void moveTo(float x0, float y0) { + p2d.moveTo(x0, y0); } public void lineJoin() {} - public void lineTo(int x1, int y1) { - p2d.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1)); + public void lineTo(float x1, float y1) { + p2d.lineTo(x1, y1); } public void close() { p2d.closePath(); @@ -144,12 +129,12 @@ public class PiscesRenderingEngine extends RenderingEngine { { strokeTo(src, at, bs, thin, normalize, antialias, new LineSink() { - public void moveTo(int x0, int y0) { - consumer.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0)); + public void moveTo(float x0, float y0) { + consumer.moveTo(x0, y0); } public void lineJoin() {} - public void lineTo(int x1, int y1) { - consumer.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1)); + public void lineTo(float x1, float y1) { + consumer.lineTo(x1, y1); } public void close() { consumer.closePath(); @@ -181,6 +166,7 @@ public class PiscesRenderingEngine extends RenderingEngine { strokeTo(src, at, lw, + normalize, bs.getEndCap(), bs.getLineJoin(), bs.getMiterLimit(), @@ -258,6 +244,7 @@ public class PiscesRenderingEngine extends RenderingEngine { void strokeTo(Shape src, AffineTransform at, float width, + boolean normalize, int caps, int join, float miterlimit, @@ -265,32 +252,16 @@ public class PiscesRenderingEngine extends RenderingEngine { float dashphase, LineSink lsink) { - Transform4 t4; - - if (at == null || at.isIdentity()) { - t4 = IdentT4; - } else { - t4 = new Transform4(FloatToS15_16((float) at.getScaleX()), - FloatToS15_16((float) at.getShearX()), - FloatToS15_16((float) at.getShearY()), - FloatToS15_16((float) at.getScaleY())); + float a00 = 1f, a01 = 0f, a10 = 0f, a11 = 1f; + if (at != null && !at.isIdentity()) { + a00 = (float)at.getScaleX(); + a01 = (float)at.getShearX(); + a10 = (float)at.getShearY(); + a11 = (float)at.getScaleY(); } - - lsink = new Stroker(lsink, - FloatToS15_16(width), - caps, - join, - FloatToS15_16(miterlimit), - t4); + lsink = new Stroker(lsink, width, caps, join, miterlimit, a00, a01, a10, a11); if (dashes != null) { - int fdashes[] = new int[dashes.length]; - for (int i = 0; i < dashes.length; i++) { - fdashes[i] = FloatToS15_16(dashes[i]); - } - lsink = new Dasher(lsink, - fdashes, - FloatToS15_16(dashphase), - t4); + lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11); } PathIterator pi = src.getPathIterator(at, defaultFlat); @@ -302,13 +273,11 @@ public class PiscesRenderingEngine extends RenderingEngine { while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case PathIterator.SEG_MOVETO: - lsink.moveTo(FloatToS15_16(coords[0]), - FloatToS15_16(coords[1])); + lsink.moveTo(coords[0], coords[1]); break; case PathIterator.SEG_LINETO: lsink.lineJoin(); - lsink.lineTo(FloatToS15_16(coords[0]), - FloatToS15_16(coords[1])); + lsink.lineTo(coords[0], coords[1]); break; case PathIterator.SEG_CLOSE: lsink.lineJoin(); @@ -378,17 +347,19 @@ public class PiscesRenderingEngine extends RenderingEngine { int bbox[]) { PiscesCache pc = PiscesCache.createInstance(); - Renderer r = new Renderer(); - r.setCache(pc); - r.setAntialiasing(3, 3); - r.beginRendering(clip.getLoX(), clip.getLoY(), - clip.getWidth(), clip.getHeight()); + Renderer r; if (bs == null) { PathIterator pi = s.getPathIterator(at, defaultFlat); - r.setWindingRule(pi.getWindingRule()); + r = new Renderer(3, 3, + clip.getLoX(), clip.getLoY(), + clip.getWidth(), clip.getHeight(), + pi.getWindingRule(), pc); pathTo(pi, r); } else { - r.setWindingRule(PathIterator.WIND_NON_ZERO); + r = new Renderer(3, 3, + clip.getLoX(), clip.getLoY(), + clip.getWidth(), clip.getHeight(), + PathIterator.WIND_NON_ZERO, pc); strokeTo(s, at, bs, thin, normalize, true, r); } r.endRendering(); diff --git a/jdk/src/share/classes/sun/java2d/pisces/Renderer.java b/jdk/src/share/classes/sun/java2d/pisces/Renderer.java index fa58146a5ad..9768e90a77b 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/Renderer.java +++ b/jdk/src/share/classes/sun/java2d/pisces/Renderer.java @@ -25,447 +25,441 @@ package sun.java2d.pisces; -public class Renderer extends LineSink { +import java.util.Arrays; + +public class Renderer implements LineSink { + +/////////////////////////////////////////////////////////////////////////////// +// Scan line iterator and edge crossing data. +////////////////////////////////////////////////////////////////////////////// + + private int[] crossings; + + // This is an array of indices into the edge array. It is initialized to + // [i * SIZEOF_STRUCT_EDGE for i in range(0, edgesSize/SIZEOF_STRUCT_EDGE)] + // (where range(i, j) is i,i+1,...,j-1 -- just like in python). + // The reason for keeping this is because we need the edges array sorted + // by y0, but we don't want to move all that data around, so instead we + // sort the indices into the edge array, and use edgeIndices to access + // the edges array. This is meant to simulate a pointer array (hence the name) + private int[] edgePtrs; + + // crossing bounds. The bounds are not necessarily tight (the scan line + // at minY, for example, might have no crossings). The x bounds will + // be accumulated as crossings are computed. + private int minY, maxY; + private int minX, maxX; + private int nextY; + + // indices into the edge pointer list. They indicate the "active" sublist in + // the edge list (the portion of the list that contains all the edges that + // cross the next scan line). + private int lo, hi; + + private static final int INIT_CROSSINGS_SIZE = 50; + private void ScanLineItInitialize() { + crossings = new int[INIT_CROSSINGS_SIZE]; + edgePtrs = new int[edgesSize / SIZEOF_STRUCT_EDGE]; + for (int i = 0; i < edgePtrs.length; i++) { + edgePtrs[i] = i * SIZEOF_STRUCT_EDGE; + } + + qsort(0, edgePtrs.length - 1); + + // We don't care if we clip some of the line off with ceil, since + // no scan line crossings will be eliminated (in fact, the ceil is + // the y of the first scan line crossing). + nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY)); + maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY)); + + for (lo = 0; lo < edgePtrs.length && edges[edgePtrs[lo]+Y1] <= nextY; lo++) + ; + for (hi = lo; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++) + ; // the active list is *edgePtrs[lo] (inclusive) *edgePtrs[hi] (exclusive) + for (int i = lo; i < hi; i++) { + setCurY(edgePtrs[i], nextY); + } + + // We accumulate X in the iterator because accumulating it in addEdge + // like we do with Y does not do much good: if there's an edge + // (0,0)->(1000,10000), and if y gets clipped to 1000, then the x + // bound should be 100, but the accumulator from addEdge would say 1000, + // so we'd still have to accumulate the X bounds as we add crossings. + minX = boundsMinX; + maxX = boundsMaxX; + } + + private int ScanLineItCurrentY() { + return nextY - 1; + } + + private int ScanLineItGoToNextYAndComputeCrossings() { + // we go through the active list and remove the ones that don't cross + // the nextY scanline. + int crossingIdx = 0; + for (int i = lo; i < hi; i++) { + if (edges[edgePtrs[i]+Y1] <= nextY) { + edgePtrs[i] = edgePtrs[lo++]; + } + } + if (hi - lo > crossings.length) { + int newSize = Math.max(hi - lo, crossings.length * 2); + crossings = Arrays.copyOf(crossings, newSize); + } + // Now every edge between lo and hi crosses nextY. Compute it's + // crossing and put it in the crossings array. + for (int i = lo; i < hi; i++) { + addCrossing(nextY, getCurCrossing(edgePtrs[i]), (int)edges[edgePtrs[i]+OR], crossingIdx); + gotoNextY(edgePtrs[i]); + crossingIdx++; + } + + nextY++; + // Expand active list to include new edges. + for (; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++) { + setCurY(edgePtrs[hi], nextY); + } + + Arrays.sort(crossings, 0, crossingIdx); + return crossingIdx; + } + + private boolean ScanLineItHasNext() { + return nextY < maxY; + } + + private void addCrossing(int y, int x, int or, int idx) { + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + x <<= 1; + crossings[idx] = ((or == 1) ? (x | 0x1) : x); + } + + + // quicksort implementation for sorting the edge indices ("pointers") + // by increasing y0. first, last are indices into the "pointer" array + // It sorts the pointer array from first (inclusive) to last (inclusive) + private void qsort(int first, int last) { + if (last > first) { + int p = partition(first, last); + if (first < p - 1) { + qsort(first, p - 1); + } + if (p < last) { + qsort(p, last); + } + } + } + + // i, j are indices into edgePtrs. + private int partition(int i, int j) { + int pivotVal = edgePtrs[i]; + while (i <= j) { + // edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C + while (edges[edgePtrs[i]+CURY] < edges[pivotVal+CURY]) { i++; } + while (edges[edgePtrs[j]+CURY] > edges[pivotVal+CURY]) { j--; } + if (i <= j) { + int tmp = edgePtrs[i]; + edgePtrs[i] = edgePtrs[j]; + edgePtrs[j] = tmp; + i++; + j--; + } + } + return i; + } + +//============================================================================ + + +////////////////////////////////////////////////////////////////////////////// +// EDGE LIST +////////////////////////////////////////////////////////////////////////////// + + private static final int INIT_NUM_EDGES = 1000; + private static final int SIZEOF_STRUCT_EDGE = 5; + + // The following array is a poor man's struct array: + // it simulates a struct array by having + // edges[SIZEOF_STRUCT_EDGE * i + j] be the jth field in the ith element + // of an array of edge structs. + private float[] edges; + private int edgesSize; // size of the edge list. + private static final int Y1 = 0; + private static final int SLOPE = 1; + private static final int OR = 2; // the orientation. This can be -1 or 1. + // -1 means up, 1 means down. + private static final int CURY = 3; // j = 5 corresponds to the "current Y". + // Each edge keeps track of the last scanline + // crossing it computed, and this is the y coord of + // that scanline. + private static final int CURX = 4; //the x coord of the current crossing. + + // Note that while the array is declared as a float[] not all of it's + // elements should be floats. currentY and Orientation should be ints (or int and + // byte respectively), but they all need to be the same type. This isn't + // really a problem because floats can represent exactly all 23 bit integers, + // which should be more than enough. + // Note, also, that we only need x1 for slope computation, so we don't need + // to store it. x0, y0 don't need to be stored either. They can be put into + // curx, cury, and it's ok if they're lost when curx and cury are changed. + // We take this undeniably ugly and error prone approach (instead of simply + // making an Edge class) for performance reasons. Also, it would probably be nicer + // to have one array for each field, but that would defeat the purpose because + // it would make poor use of the processor cache, since we tend to access + // all the fields for one edge at a time. + + private float edgeMinY; + private float edgeMaxY; + + + private void addEdge(float x0, float y0, float x1, float y1) { + float or = (y0 < y1) ? 1f : -1f; // orientation: 1 = UP; -1 = DOWN + if (or == -1) { + float tmp = y0; + y0 = y1; + y1 = tmp; + tmp = x0; + x0 = x1; + x1 = tmp; + } + // skip edges that don't cross a scanline + if (Math.ceil(y0) >= Math.ceil(y1)) { + return; + } + + int newSize = edgesSize + SIZEOF_STRUCT_EDGE; + if (edges.length < newSize) { + edges = Arrays.copyOf(edges, newSize * 2); + } + edges[edgesSize+CURX] = x0; + edges[edgesSize+CURY] = y0; + edges[edgesSize+Y1] = y1; + edges[edgesSize+SLOPE] = (x1 - x0) / (y1 - y0); + edges[edgesSize+OR] = or; + // the crossing values can't be initialized meaningfully yet. This + // will have to wait until setCurY is called + edgesSize += SIZEOF_STRUCT_EDGE; + + // Accumulate edgeMinY and edgeMaxY + if (y0 < edgeMinY) { edgeMinY = y0; } + if (y1 > edgeMaxY) { edgeMaxY = y1; } + } + + // As far as the following methods care, this edges extends to infinity. + // They can compute the x intersect of any horizontal line. + // precondition: idx is the index to the start of the desired edge. + // So, if the ith edge is wanted, idx should be SIZEOF_STRUCT_EDGE * i + private void setCurY(int idx, int y) { + // compute the x crossing of edge at idx and horizontal line y + // currentXCrossing = (y - y0)*slope + x0 + edges[idx + CURX] = (y - edges[idx + CURY]) * edges[idx + SLOPE] + edges[idx+CURX]; + edges[idx + CURY] = (float)y; + } + + private void gotoNextY(int idx) { + edges[idx + CURY] += 1f; // i.e. curY += 1 + edges[idx + CURX] += edges[idx + SLOPE]; // i.e. curXCrossing += slope + } + + private int getCurCrossing(int idx) { + return (int)edges[idx + CURX]; + } +//==================================================================================== + public static final int WIND_EVEN_ODD = 0; public static final int WIND_NON_ZERO = 1; - // Initial edge list size - // IMPL_NOTE - restore size after growth - public static final int INITIAL_EDGES = 1000; - - // Recommended maximum scratchpad sizes. The arrays will grow - // larger if needed, but when finished() is called they will be released - // if they have grown larger than these sizes. - public static final int DEFAULT_INDICES_SIZE = 8192; - public static final int DEFAULT_CROSSINGS_SIZE = 32*1024; - // Antialiasing - private int SUBPIXEL_LG_POSITIONS_X; - private int SUBPIXEL_LG_POSITIONS_Y; - private int SUBPIXEL_MASK_X; - private int SUBPIXEL_MASK_Y; - private int SUBPIXEL_POSITIONS_X; - private int SUBPIXEL_POSITIONS_Y; - int MAX_AA_ALPHA; - private int MAX_AA_ALPHA_DENOM; - private int HALF_MAX_AA_ALPHA_DENOM; - private int XSHIFT; - private int YSHIFT; - private int YSTEP; - private int HYSTEP; - private int YMASK; - - private static final int MIN_QUAD_OPT_WIDTH = 100 << 16; + final private int SUBPIXEL_LG_POSITIONS_X; + final private int SUBPIXEL_LG_POSITIONS_Y; + final private int SUBPIXEL_POSITIONS_X; + final private int SUBPIXEL_POSITIONS_Y; + final private int SUBPIXEL_MASK_X; + final private int SUBPIXEL_MASK_Y; + final int MAX_AA_ALPHA; // Cache to store RLE-encoded coverage mask of the current primitive - PiscesCache cache; + final PiscesCache cache; - // Bounds of the drawing region, at S15.16 precsion - private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; - - // Bounds of the current primitive, at subsample precision - private int rasterMinX, rasterMaxX, rasterMinY, rasterMaxY; + // Bounds of the drawing region, at subpixel precision. + final private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; // Pixel bounding box for current primitive - private int bboxX0, bboxY0, bboxX1, bboxY1; + private int pix_bboxX0, pix_bboxY0, pix_bboxX1, pix_bboxY1; // Current winding rule - private int windingRule; + final private int windingRule; // Current drawing position, i.e., final point of last segment - private int x0, y0; + private float x0, y0; // Position of most recent 'moveTo' command - private int sx0, sy0; + private float pix_sx0, pix_sy0; - // Buffer to be filled with one row's worth of alpha values - private byte[] rowAA; // needs to be short if 16x16 subsampling - - // Track the number of vertical extrema of the incoming edge list - // in order to determine the maximum number of crossings of a - // scanline - private int firstOrientation; - private int lastOrientation; - private int flips; - - // Parameters for emitRow - private int alphaWidth; - - public Renderer() { - } - - public void setAntialiasing(int subpixelLgPositionsX, - int subpixelLgPositionsY) { + public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY, + int pix_boundsX, int pix_boundsY, + int pix_boundsWidth, int pix_boundsHeight, + int windingRule, + PiscesCache cache) { this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX; this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY; + this.SUBPIXEL_MASK_X = (1 << (SUBPIXEL_LG_POSITIONS_X)) - 1; + this.SUBPIXEL_MASK_Y = (1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1; + this.SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X); + this.SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y); + this.MAX_AA_ALPHA = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y); - this.SUBPIXEL_MASK_X = - (1 << (SUBPIXEL_LG_POSITIONS_X)) - 1; - this.SUBPIXEL_MASK_Y = - (1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1; - this.SUBPIXEL_POSITIONS_X = - 1 << (SUBPIXEL_LG_POSITIONS_X); - this.SUBPIXEL_POSITIONS_Y = - 1 << (SUBPIXEL_LG_POSITIONS_Y); - this.MAX_AA_ALPHA = - (SUBPIXEL_POSITIONS_X*SUBPIXEL_POSITIONS_Y); - this.MAX_AA_ALPHA_DENOM = 255*MAX_AA_ALPHA; - this.HALF_MAX_AA_ALPHA_DENOM = MAX_AA_ALPHA_DENOM/2; - this.XSHIFT = 16 - SUBPIXEL_LG_POSITIONS_X; - this.YSHIFT = 16 - SUBPIXEL_LG_POSITIONS_Y; - this.YSTEP = 1 << YSHIFT; - this.HYSTEP = 1 << (YSHIFT - 1); - this.YMASK = ~(YSTEP - 1); - } + this.edges = new float[SIZEOF_STRUCT_EDGE * INIT_NUM_EDGES]; + edgeMinY = Float.POSITIVE_INFINITY; + edgeMaxY = Float.NEGATIVE_INFINITY; + edgesSize = 0; - public int getSubpixelLgPositionsX() { - return SUBPIXEL_LG_POSITIONS_X; - } - - public int getSubpixelLgPositionsY() { - return SUBPIXEL_LG_POSITIONS_Y; - } - - public void setWindingRule(int windingRule) { this.windingRule = windingRule; + this.cache = cache; + + this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X; + this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y; + this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X; + this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y; + + this.pix_bboxX0 = pix_boundsX; + this.pix_bboxY0 = pix_boundsY; + this.pix_bboxX1 = pix_boundsX + pix_boundsWidth; + this.pix_bboxY1 = pix_boundsY + pix_boundsHeight; } - public int getWindingRule() { - return windingRule; + private float tosubpixx(float pix_x) { + return pix_x * SUBPIXEL_POSITIONS_X; + } + private float tosubpixy(float pix_y) { + return pix_y * SUBPIXEL_POSITIONS_Y; } - public void beginRendering(int boundsX, int boundsY, - int boundsWidth, int boundsHeight) { - lastOrientation = 0; - flips = 0; - - resetEdges(); - - this.boundsMinX = boundsX << 16; - this.boundsMinY = boundsY << 16; - this.boundsMaxX = (boundsX + boundsWidth) << 16; - this.boundsMaxY = (boundsY + boundsHeight) << 16; - - this.bboxX0 = boundsX; - this.bboxY0 = boundsY; - this.bboxX1 = boundsX + boundsWidth; - this.bboxY1 = boundsY + boundsHeight; - } - - public void moveTo(int x0, int y0) { - // System.out.println("Renderer: moveTo " + x0/65536.0 + " " + y0/65536.0); + public void moveTo(float pix_x0, float pix_y0) { close(); - this.sx0 = this.x0 = x0; - this.sy0 = this.y0 = y0; - this.lastOrientation = 0; + this.pix_sx0 = pix_x0; + this.pix_sy0 = pix_y0; + this.y0 = tosubpixy(pix_y0); + this.x0 = tosubpixx(pix_x0); } - public void lineJoin() { - // System.out.println("Renderer: lineJoin"); - // do nothing - } + public void lineJoin() { /* do nothing */ } - public void lineTo(int x1, int y1) { - // System.out.println("Renderer: lineTo " + x1/65536.0 + " " + y1/65536.0); + public void lineTo(float pix_x1, float pix_y1) { + float x1 = tosubpixx(pix_x1); + float y1 = tosubpixy(pix_y1); // Ignore horizontal lines - // Next line will count flip if (y0 == y1) { this.x0 = x1; return; } - int orientation = (y0 < y1) ? 1 : -1; - if (lastOrientation == 0) { - firstOrientation = orientation; - } else if (orientation != lastOrientation) { - ++flips; - } - lastOrientation = orientation; - - // Bias Y by 1 ULP so endpoints never lie on a scanline - addEdge(x0, y0 | 0x1, x1, y1 | 0x1); + addEdge(x0, y0, x1, y1); this.x0 = x1; this.y0 = y1; } public void close() { - // System.out.println("Renderer: close"); - - int orientation = lastOrientation; - if (y0 != sy0) { - orientation = (y0 < sy0) ? 1 : -1; - } - if (orientation != firstOrientation) { - ++flips; - } - lineTo(sx0, sy0); + // lineTo expects its input in pixel coordinates. + lineTo(pix_sx0, pix_sy0); } public void end() { close(); - // System.out.println("Renderer: end"); - // do nothing - } - - // Scan convert a single edge - private void computeCrossingsForEdge(int index, - int boundsMinY, int boundsMaxY) { - int iy0 = edges[index + 1]; - int iy1 = edges[index + 3]; - - // Clip to valid Y range - int clipy0 = (iy0 > boundsMinY) ? iy0 : boundsMinY; - int clipy1 = (iy1 < boundsMaxY) ? iy1 : boundsMaxY; - - int minY = ((clipy0 + HYSTEP) & YMASK) + HYSTEP; - int maxY = ((clipy1 - HYSTEP) & YMASK) + HYSTEP; - - // IMPL_NOTE - If line falls outside the valid X range, could - // draw a vertical line instead - - // Exit if no scanlines are crossed - if (minY > maxY) { - return; - } - - // Scan convert line using a DDA approach - - int ix0 = edges[index]; - int ix1 = edges[index + 2]; - long dx = ((long) ix1) - ix0; - long dy = ((long) iy1) - iy0; - - // Compute first crossing point at y = minY - int orientation = edges[index + 4]; - int y = minY; - long lx = (((long) y) - iy0)*dx/dy + ix0; - addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation); - - // Advance y to next scanline, exit if past endpoint - y += YSTEP; - if (y > maxY) { - return; - } - - // Compute xstep only if additional scanlines are crossed - // For each scanline, add xstep to lx and YSTEP to y and - // emit the new crossing - long xstep = ((long)YSTEP*dx)/dy; - for (; y <= maxY; y += YSTEP) { - lx += xstep; - addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation); - } - } - - private void computeBounds() { - rasterMinX = crossingMinX & ~SUBPIXEL_MASK_X; - rasterMaxX = crossingMaxX | SUBPIXEL_MASK_X; - rasterMinY = crossingMinY & ~SUBPIXEL_MASK_Y; - rasterMaxY = crossingMaxY | SUBPIXEL_MASK_Y; - - // If nothing was drawn, we have: - // minX = Integer.MAX_VALUE and maxX = Integer.MIN_VALUE - // so nothing to render - if (rasterMinX > rasterMaxX || rasterMinY > rasterMaxY) { - rasterMinX = 0; - rasterMaxX = -1; - rasterMinY = 0; - rasterMaxY = -1; - return; - } - - if (rasterMinX < boundsMinX >> XSHIFT) { - rasterMinX = boundsMinX >> XSHIFT; - } - if (rasterMinY < boundsMinY >> YSHIFT) { - rasterMinY = boundsMinY >> YSHIFT; - } - if (rasterMaxX > boundsMaxX >> XSHIFT) { - rasterMaxX = boundsMaxX >> XSHIFT; - } - if (rasterMaxY > boundsMaxY >> YSHIFT) { - rasterMaxY = boundsMaxY >> YSHIFT; - } - } - - private int clamp(int x, int min, int max) { - if (x < min) { - return min; - } else if (x > max) { - return max; - } - return x; } private void _endRendering() { - if (flips == 0) { - bboxX0 = bboxY0 = 0; - bboxX1 = bboxY1 = -1; - return; - } + // Mask to determine the relevant bit of the crossing sum + // 0x1 if EVEN_ODD, all bits if NON_ZERO + int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0; - // Special case for filling a single rect with a flat, opaque color - // REMIND: This special case was not originally written to fill a - // cache object and called directly to a Blit - it needs some code - // to fill the cache instead to be useful for this usage... - if (false /* Does not work with cache (yet?) */ && - edgeIdx == 10 && - edges[0] == edges[2] && - edges[1] == edges[6] && - edges[3] == edges[8] && - edges[5] == edges[7] && - Math.abs(edges[0] - edges[5]) > MIN_QUAD_OPT_WIDTH) - { + // add 1 to better deal with the last pixel in a pixel row. + int width = ((boundsMaxX - boundsMinX) >> SUBPIXEL_LG_POSITIONS_X) + 1; + byte[] alpha = new byte[width+1]; - int x0 = edges[0] >> XSHIFT; - int y0 = edges[1] >> YSHIFT; - int x1 = edges[5] >> XSHIFT; - int y1 = edges[3] >> YSHIFT; + // Now we iterate through the scanlines. We must tell emitRow the coord + // of the first non-transparent pixel, so we must keep accumulators for + // the first and last pixels of the section of the current pixel row + // that we will emit. + // We also need to accumulate pix_bbox*, but the iterator does it + // for us. We will just get the values from it once this loop is done + int pix_maxX = Integer.MIN_VALUE; + int pix_minX = Integer.MAX_VALUE; - if (x0 > x1) { - int tmp = x0; - x0 = x1; - x1 = tmp; - } - if (y0 > y1) { - int tmp = y0; - y0 = y1; - y1 = tmp; + int y = boundsMinY; // needs to be declared here so we emit the last row properly. + ScanLineItInitialize(); + for ( ; ScanLineItHasNext(); ) { + int numCrossings = ScanLineItGoToNextYAndComputeCrossings(); + y = ScanLineItCurrentY(); + + if (numCrossings > 0) { + int lowx = crossings[0] >> 1; + int highx = crossings[numCrossings - 1] >> 1; + int x0 = Math.max(lowx, boundsMinX); + int x1 = Math.min(highx, boundsMaxX); + + pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X); + pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X); } - int bMinX = this.boundsMinX >> XSHIFT; - int bMinY = this.boundsMinY >> YSHIFT; - int bMaxX = this.boundsMaxX >> XSHIFT; - int bMaxY = this.boundsMaxY >> YSHIFT; + int sum = 0; + int prev = boundsMinX; + for (int i = 0; i < numCrossings; i++) { + int curxo = crossings[i]; + int curx = curxo >> 1; + int crorientation = ((curxo & 0x1) == 0x1) ? 1 : -1; + if ((sum & mask) != 0) { + int x0 = Math.max(prev, boundsMinX); + int x1 = Math.min(curx, boundsMaxX); + if (x0 < x1) { + x0 -= boundsMinX; // turn x0, x1 from coords to indeces + x1 -= boundsMinX; // in the alpha array. - // Clip to image bounds in supersampled coordinates - x0 = clamp(x0, bMinX, bMaxX); - x1 = clamp(x1, bMinX, bMaxX); - y0 = clamp(y0, bMinY, bMaxY); - y1 = clamp(y1, bMinY, bMaxY); + int pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X; + int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X; - /* - * REMIND: Need to fill the cache here instead... - Blit.fillRectSrcOver(this, - imageData, imageType, - imageOffset, - imageScanlineStride, imagePixelStride, - width, height, - x0, y0, x1, y1, - cred, cgreen, cblue); - */ - - bboxX0 = x0 >> SUBPIXEL_LG_POSITIONS_X; - bboxY0 = y0 >> SUBPIXEL_LG_POSITIONS_Y; - bboxX1 = (x1 + SUBPIXEL_POSITIONS_X - 1) - >> SUBPIXEL_LG_POSITIONS_X; - bboxY1 = (y1 + SUBPIXEL_POSITIONS_Y - 1) - >> SUBPIXEL_LG_POSITIONS_Y; - - return; - } - - int minY = (edgeMinY > boundsMinY) ? edgeMinY : boundsMinY; - int maxY = (edgeMaxY < boundsMaxY) ? edgeMaxY : boundsMaxY; - - // Check for empty intersection of primitive with the drawing area - if (minY > maxY) { - bboxX0 = bboxY0 = 0; - bboxX1 = bboxY1 = -1; - return; - } - - // Compute Y extent in subpixel coordinates - int iminY = (minY >> YSHIFT) & ~SUBPIXEL_MASK_Y; - int imaxY = (maxY >> YSHIFT) | SUBPIXEL_MASK_Y; - int yextent = (imaxY - iminY) + 1; - - // Maximum number of crossings - int size = flips*yextent; - - int bmax = (boundsMaxY >> YSHIFT) - 1; - if (imaxY > bmax) { - imaxY = bmax; - } - - // Initialize X bounds, will be refined for each strip - bboxX0 = Integer.MAX_VALUE; - bboxX1 = Integer.MIN_VALUE; - - // Set Y bounds - bboxY0 = iminY >> SUBPIXEL_LG_POSITIONS_Y; - bboxY1 = (imaxY + SUBPIXEL_POSITIONS_Y - 1) >> SUBPIXEL_LG_POSITIONS_Y; - - // Compute number of rows that can be processing using - // a crossings table no larger than DEFAULT_CROSSINGS_SIZE. - // However, we must process at least one row, so we grow the table - // temporarily if needed. This would require an object with a - // huge number of flips. - int rows = DEFAULT_CROSSINGS_SIZE/(flips*SUBPIXEL_POSITIONS_Y); - rows = Math.min(rows, yextent); - rows = Math.max(rows, 1); - for (int i = iminY; i <= imaxY; i += rows*SUBPIXEL_POSITIONS_Y) { - // Compute index of last scanline to be processed in this pass - int last = Math.min(i + rows*SUBPIXEL_POSITIONS_Y - 1, imaxY); - setCrossingsExtents(i, last, flips); - - int bminY = i << YSHIFT; - int bmaxY = (last << YSHIFT) | ~YMASK; - - // Process edges from the edge list - int maxIdx = edgeIdx; - for (int index = 0; index < maxIdx; index += 5) { - // Test y1 < min: - // - // If edge lies entirely above current strip, - // discard it - if (edges[index + 3] < bminY) { - // Overwrite the edge with the last edge - edgeIdx -= 5; - int fidx = edgeIdx; - int tidx = index; - edges[tidx++] = edges[fidx++]; - edges[tidx++] = edges[fidx++]; - edges[tidx++] = edges[fidx++]; - edges[tidx++] = edges[fidx++]; - edges[tidx ] = edges[fidx ]; - - maxIdx -= 5; - index -= 5; - continue; + if (pix_x == pix_xmaxm1) { + // Start and end in same pixel + alpha[pix_x] += (x1 - x0); + alpha[pix_x+1] -= (x1 - x0); + } else { + int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; + alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X); + alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X); + alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X); + alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X); + } + } } - - // Test y0 > max: - // - // If edge lies entirely below current strip, - // skip it for now - if (edges[index + 1] > bmaxY) { - continue; - } - - computeCrossingsForEdge(index, bminY, bmaxY); + sum += crorientation; + prev = curx; } - computeBounds(); - if (rasterMaxX < rasterMinX) { - continue; + if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) { + emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX); + pix_minX = Integer.MAX_VALUE; + pix_maxX = Integer.MIN_VALUE; } - - bboxX0 = Math.min(bboxX0, - rasterMinX >> SUBPIXEL_LG_POSITIONS_X); - bboxX1 = Math.max(bboxX1, - (rasterMaxX + SUBPIXEL_POSITIONS_X - 1) - >> SUBPIXEL_LG_POSITIONS_X); - renderStrip(); } - // Free up any unusually large scratchpad memory used by the - // preceding primitive - crossingListFinished(); + // Emit final row + if (pix_maxX >= pix_minX) { + emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX); + } + pix_bboxX0 = minX >> SUBPIXEL_LG_POSITIONS_X; + pix_bboxX1 = maxX >> SUBPIXEL_LG_POSITIONS_X; + pix_bboxY0 = minY >> SUBPIXEL_LG_POSITIONS_Y; + pix_bboxY1 = maxY >> SUBPIXEL_LG_POSITIONS_Y; } + public void endRendering() { // Set up the cache to accumulate the bounding box if (cache != null) { @@ -478,176 +472,31 @@ public class Renderer extends LineSink { _endRendering(); } - public void getBoundingBox(int[] bbox) { - bbox[0] = bboxX0; - bbox[1] = bboxY0; - bbox[2] = bboxX1 - bboxX0; - bbox[3] = bboxY1 - bboxY0; + public void getBoundingBox(int[] pix_bbox) { + pix_bbox[0] = pix_bboxX0; + pix_bbox[1] = pix_bboxY0; + pix_bbox[2] = pix_bboxX1 - pix_bboxX0; + pix_bbox[3] = pix_bboxY1 - pix_bboxY0; } - private void renderStrip() { - // Grow rowAA according to the raster width - int width = (rasterMaxX - rasterMinX + 1) >> SUBPIXEL_LG_POSITIONS_X; - alphaWidth = width; - - // Allocate one extra entry in rowAA to avoid a conditional in - // the rendering loop - int bufLen = width + 1; - if (this.rowAA == null || this.rowAA.length < bufLen) { - this.rowAA = new byte[bufLen]; - } - - // Mask to determine the relevant bit of the crossing sum - // 0x1 if EVEN_ODD, all bits if NON_ZERO - int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0; - - int y = 0; - int prevY = rasterMinY - 1; - - int minX = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - - iterateCrossings(); - while (hasMoreCrossingRows()) { - y = crossingY; - - // Emit any skipped rows - for (int j = prevY + 1; j < y; j++) { - if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || - (j == rasterMaxY)) { - emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, 0, -1); - } - } - prevY = y; - - if (crossingRowIndex < crossingRowCount) { - int lx = crossings[crossingRowOffset + crossingRowIndex]; - lx >>= 1; - int hx = crossings[crossingRowOffset + crossingRowCount - 1]; - hx >>= 1; - int x0 = lx > rasterMinX ? lx : rasterMinX; - int x1 = hx < rasterMaxX ? hx : rasterMaxX; - x0 -= rasterMinX; - x1 -= rasterMinX; - - minX = Math.min(minX, x0 >> SUBPIXEL_LG_POSITIONS_X); - maxX = Math.max(maxX, x1 >> SUBPIXEL_LG_POSITIONS_X); - } - - int sum = 0; - int prev = rasterMinX; - while (crossingRowIndex < crossingRowCount) { - int crxo = crossings[crossingRowOffset + crossingRowIndex]; - crossingRowIndex++; - - int crx = crxo >> 1; - int crorientation = ((crxo & 0x1) == 0x1) ? 1 : -1; - - if ((sum & mask) != 0) { - // Clip to active X range, if x1 < x0 loop will - // have no effect - int x0 = prev > rasterMinX ? prev : rasterMinX; - int x1 = crx < rasterMaxX ? crx : rasterMaxX; - - // Empty spans - if (x1 > x0) { - x0 -= rasterMinX; - x1 -= rasterMinX; - - // Accumulate alpha, equivalent to: - // for (int x = x0; x < x1; x++) { - // ++rowAA[x >> SUBPIXEL_LG_POSITIONS_X]; - // } - // - // In the middle of the span, we can update a full - // pixel at a time (i.e., SUBPIXEL_POSITIONS_X - // subpixels) - - int x = x0 >> SUBPIXEL_LG_POSITIONS_X; - int xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X; - if (x == xmaxm1) { - // Start and end in same pixel - rowAA[x] += x1 - x0; - } else { - // Start and end in different pixels - rowAA[x++] += SUBPIXEL_POSITIONS_X - - (x0 & SUBPIXEL_MASK_X); - int xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; - while (x < xmax) { - rowAA[x++] += SUBPIXEL_POSITIONS_X; - } - // Note - at this point it is possible that - // x == width, which implies that - // x1 & SUBPIXEL_MASK_X == 0. We allocate - // one extra entry in rowAA so this - // assignment will be harmless. The alternative - // is an extra conditional here, or some other - // scheme to deal with the last pixel better. - rowAA[x] += x1 & SUBPIXEL_MASK_X; - } - } - } - sum += crorientation; - prev = crx; - } - - // Every SUBPIXEL_POSITIONS rows, output an antialiased row - if (((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || - (y == rasterMaxY)) { - emitRow(y >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX); - minX = Integer.MAX_VALUE; - maxX = Integer.MIN_VALUE; - } - } - - // Emit final row - for (int j = prevY + 1; j <= rasterMaxY; j++) { - if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || - (j == rasterMaxY)) { - emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX); - minX = Integer.MAX_VALUE; - maxX = Integer.MIN_VALUE; - } - } - } - - private void clearAlpha(byte[] alpha, - int width, - int minX, int maxX) { - if (maxX >= minX) { - int w = maxX - minX + 1; - if (w + minX > width) { - w = width - minX; - } - - int aidx = minX; - for (int i = 0; i < w; i++, aidx++) { - alpha[aidx] = (byte)0; - } - } - } - - private void emitRow(int y, int minX, int maxX) { + private void emitRow(byte[] alphaRow, int pix_y, int pix_from, int pix_to) { // Copy rowAA data into the cache if one is present if (cache != null) { - if (maxX >= minX) { - int x0 = minX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); - int x1 = maxX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); + if (pix_to >= pix_from) { + cache.startRow(pix_y, pix_from, pix_to); - cache.startRow(y, x0, x1); - int srcIdx = minX; + // Perform run-length encoding and store results in the cache + int from = pix_from - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X); + int to = pix_to - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X); - // Perform run-length encoding - // and store results in the cache - byte startVal = rowAA[srcIdx++]; int runLen = 1; - while (srcIdx <= maxX) { - byte nextVal = rowAA[srcIdx++]; + byte startVal = alphaRow[from]; + for (int i = from + 1; i <= to; i++) { + byte nextVal = (byte)(startVal + alphaRow[i]); if (nextVal == startVal && runLen < 255) { - ++runLen; + runLen++; } else { cache.addRLERun(startVal, runLen); - runLen = 1; startVal = nextVal; } @@ -656,190 +505,6 @@ public class Renderer extends LineSink { cache.addRLERun((byte)0, 0); } } - - clearAlpha(rowAA, - alphaWidth, - minX, maxX); - } - - public void setCache(PiscesCache cache) { - this.cache = cache; - } - - // Edge list data - - private int[] edges = new int[5*INITIAL_EDGES]; - private int edgeIdx = 0; - private int edgeMinY = Integer.MAX_VALUE; - private int edgeMaxY = Integer.MIN_VALUE; - - private void addEdge(int x0, int y0, int x1, int y1) { - int newLen = edgeIdx + 5; - if (edges.length < newLen) { - int[] tmp = new int[Math.max(11*edges.length/10, newLen)]; - System.arraycopy(edges, 0, tmp, 0, edgeIdx); - this.edges = tmp; - } - - int orientation = 1; - if (y0 > y1) { - int tmp = y0; - y0 = y1; - y1 = tmp; - - orientation = -1; - } - - // Skip edges that don't cross a subsampled scanline - int eminY = ((y0 + HYSTEP) & YMASK); - int emaxY = ((y1 - HYSTEP) & YMASK); - if (eminY > emaxY) { - return; - } - - if (orientation == -1) { - int tmp = x0; - x0 = x1; - x1 = tmp; - } - - edges[edgeIdx++] = x0; - edges[edgeIdx++] = y0; - edges[edgeIdx++] = x1; - edges[edgeIdx++] = y1; - edges[edgeIdx++] = orientation; - - // Update Y bounds of primitive - if (y0 < edgeMinY) { - edgeMinY = y0; - } - if (y1 > edgeMaxY) { - edgeMaxY = y1; - } - } - - private void resetEdges() { - this.edgeIdx = 0; - this.edgeMinY = Integer.MAX_VALUE; - this.edgeMaxY = Integer.MIN_VALUE; - } - - // Crossing list data - - private int[] crossingIndices; - private int[] crossings; - private int crossingMinY; - private int crossingMaxY; - private int crossingMinX = Integer.MAX_VALUE; - private int crossingMaxX = Integer.MIN_VALUE; - private int crossingMaxXEntries; - private int numCrossings = 0; - private boolean crossingsSorted = false; - - private int crossingY; - private int crossingRowCount; - private int crossingRowOffset; - private int crossingRowIndex; - - private void setCrossingsExtents(int minY, int maxY, int maxXEntries) { - int yextent = maxY - minY + 1; - - // Grow indices array as needed - if (crossingIndices == null || crossingIndices.length < yextent) { - this.crossingIndices = - new int[Math.max(yextent, DEFAULT_INDICES_SIZE)]; - } - // Grow crossings array as needed - if (crossings == null || crossings.length < yextent*maxXEntries) { - this.crossings = new int[Math.max(yextent*maxXEntries, - DEFAULT_CROSSINGS_SIZE)]; - } - this.crossingMinY = minY; - this.crossingMaxY = maxY; - this.crossingMaxXEntries = maxXEntries; - resetCrossings(); - } - - private void resetCrossings() { - int yextent = crossingMaxY - crossingMinY + 1; - int start = 0; - for (int i = 0; i < yextent; i++) { - crossingIndices[i] = start; - start += crossingMaxXEntries; - } - crossingMinX = Integer.MAX_VALUE; - crossingMaxX = Integer.MIN_VALUE; - numCrossings = 0; - crossingsSorted = false; - } - - // Free sorting arrays if larger than maximum size - private void crossingListFinished() { - if (crossings != null && crossings.length > DEFAULT_CROSSINGS_SIZE) { - crossings = new int[DEFAULT_CROSSINGS_SIZE]; - } - if (crossingIndices != null && - crossingIndices.length > DEFAULT_INDICES_SIZE) - { - crossingIndices = new int[DEFAULT_INDICES_SIZE]; - } - } - - private void sortCrossings(int[] x, int off, int len) { - for (int i = off + 1; i < off + len; i++) { - int j = i; - int xj = x[j]; - int xjm1; - - while (j > off && (xjm1 = x[j - 1]) > xj) { - x[j] = xjm1; - x[j - 1] = xj; - j--; - } - } - } - - private void sortCrossings() { - int start = 0; - for (int i = 0; i <= crossingMaxY - crossingMinY; i++) { - sortCrossings(crossings, start, crossingIndices[i] - start); - start += crossingMaxXEntries; - } - } - - private void addCrossing(int y, int x, int orientation) { - if (x < crossingMinX) { - crossingMinX = x; - } - if (x > crossingMaxX) { - crossingMaxX = x; - } - - int index = crossingIndices[y - crossingMinY]++; - x <<= 1; - crossings[index] = (orientation == 1) ? (x | 0x1) : x; - - ++numCrossings; - } - - private void iterateCrossings() { - if (!crossingsSorted) { - sortCrossings(); - crossingsSorted = true; - } - crossingY = crossingMinY - 1; - crossingRowOffset = -crossingMaxXEntries; - } - - private boolean hasMoreCrossingRows() { - if (++crossingY <= crossingMaxY) { - crossingRowOffset += crossingMaxXEntries; - int y = crossingY - crossingMinY; - crossingRowCount = crossingIndices[y] - y*crossingMaxXEntries; - crossingRowIndex = 0; - return true; - } else { - return false; - } + java.util.Arrays.fill(alphaRow, (byte)0); } } diff --git a/jdk/src/share/classes/sun/java2d/pisces/Stroker.java b/jdk/src/share/classes/sun/java2d/pisces/Stroker.java index e17283a3bdc..574c460fea9 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/Stroker.java +++ b/jdk/src/share/classes/sun/java2d/pisces/Stroker.java @@ -25,7 +25,7 @@ package sun.java2d.pisces; -public class Stroker extends LineSink { +public class Stroker implements LineSink { private static final int MOVE_TO = 0; private static final int LINE_TO = 1; @@ -61,19 +61,15 @@ public class Stroker extends LineSink { */ public static final int CAP_SQUARE = 2; - LineSink output; + private final LineSink output; - int lineWidth; - int capStyle; - int joinStyle; - int miterLimit; + private final int capStyle; + private final int joinStyle; - Transform4 transform; - int m00, m01; - int m10, m11; + private final float m00, m01, m10, m11, det; - int lineWidth2; - long scaledLineWidth2; + private final float lineWidth2; + private final float scaledLineWidth2; // For any pen offset (pen_dx, pen_dy) that does not depend on // the line orientation, the pen should be transformed so that: @@ -88,143 +84,86 @@ public class Stroker extends LineSink { // // pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta)) // pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta)) - int numPenSegments; - int[] pen_dx; - int[] pen_dy; - boolean[] penIncluded; - int[] join; + private int numPenSegments; + private final float[] pen_dx; + private final float[] pen_dy; + private boolean[] penIncluded; + private final float[] join; - int[] offset = new int[2]; - int[] reverse = new int[100]; - int[] miter = new int[2]; - long miterLimitSq; + private final float[] offset = new float[2]; + private float[] reverse = new float[100]; + private final float[] miter = new float[2]; + private final float miterLimitSq; - int prev; - int rindex; - boolean started; - boolean lineToOrigin; - boolean joinToOrigin; + private int prev; + private int rindex; + private boolean started; + private boolean lineToOrigin; + private boolean joinToOrigin; - int sx0, sy0, sx1, sy1, x0, y0, x1, y1; - int mx0, my0, mx1, my1, omx, omy; - int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0; + private float sx0, sy0, sx1, sy1, x0, y0, px0, py0; + private float mx0, my0, omx, omy; - double m00_2_m01_2; - double m10_2_m11_2; - double m00_m10_m01_m11; - - /** - * Empty constructor. setOutput and - * setParameters must be called prior to calling any - * other methods. - */ - public Stroker() {} + private float m00_2_m01_2; + private float m10_2_m11_2; + private float m00_m10_m01_m11; /** * Constructs a Stroker. * * @param output an output LineSink. - * @param lineWidth the desired line width in pixels, in S15.16 - * format. + * @param lineWidth the desired line width in pixels * @param capStyle the desired end cap style, one of * CAP_BUTT, CAP_ROUND or * CAP_SQUARE. * @param joinStyle the desired line join style, one of * JOIN_MITER, JOIN_ROUND or * JOIN_BEVEL. - * @param miterLimit the desired miter limit, in S15.16 format. + * @param miterLimit the desired miter limit * @param transform a Transform4 object indicating * the transform that has been previously applied to all incoming * coordinates. This is required in order to produce consistently * shaped end caps and joins. */ public Stroker(LineSink output, - int lineWidth, + float lineWidth, int capStyle, int joinStyle, - int miterLimit, - Transform4 transform) { - setOutput(output); - setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform); - } - - /** - * Sets the output LineSink of this - * Stroker. - * - * @param output an output LineSink. - */ - public void setOutput(LineSink output) { + float miterLimit, + float m00, float m01, float m10, float m11) { this.output = output; - } - /** - * Sets the parameters of this Stroker. - * @param lineWidth the desired line width in pixels, in S15.16 - * format. - * @param capStyle the desired end cap style, one of - * CAP_BUTT, CAP_ROUND or - * CAP_SQUARE. - * @param joinStyle the desired line join style, one of - * JOIN_MITER, JOIN_ROUND or - * JOIN_BEVEL. - * @param miterLimit the desired miter limit, in S15.16 format. - * @param transform a Transform4 object indicating - * the transform that has been previously applied to all incoming - * coordinates. This is required in order to produce consistently - * shaped end caps and joins. - */ - public void setParameters(int lineWidth, - int capStyle, - int joinStyle, - int miterLimit, - Transform4 transform) { - this.lineWidth = lineWidth; - this.lineWidth2 = lineWidth >> 1; - this.scaledLineWidth2 = ((long)transform.m00*lineWidth2) >> 16; + this.lineWidth2 = lineWidth / 2; + this.scaledLineWidth2 = m00 * lineWidth2; this.capStyle = capStyle; this.joinStyle = joinStyle; - this.miterLimit = miterLimit; - this.transform = transform; - this.m00 = transform.m00; - this.m01 = transform.m01; - this.m10 = transform.m10; - this.m11 = transform.m11; + m00_2_m01_2 = m00*m00 + m01*m01; + m10_2_m11_2 = m10*m10 + m11*m11; + m00_m10_m01_m11 = m00*m10 + m01*m11; - this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01; - this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11; - this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11; + this.m00 = m00; + this.m01 = m01; + this.m10 = m10; + this.m11 = m11; + det = m00*m11 - m01*m10; - double dm00 = m00/65536.0; - double dm01 = m01/65536.0; - double dm10 = m10/65536.0; - double dm11 = m11/65536.0; - double determinant = dm00*dm11 - dm01*dm10; + float limit = miterLimit * lineWidth2 * det; + this.miterLimitSq = limit*limit; - if (joinStyle == JOIN_MITER) { - double limit = - (miterLimit/65536.0)*(lineWidth2/65536.0)*determinant; - double limitSq = limit*limit; - this.miterLimitSq = (long)(limitSq*65536.0*65536.0); - } - - this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f); - if (pen_dx == null || pen_dx.length < numPenSegments) { - this.pen_dx = new int[numPenSegments]; - this.pen_dy = new int[numPenSegments]; - this.penIncluded = new boolean[numPenSegments]; - this.join = new int[2*numPenSegments]; - } + this.numPenSegments = (int)(3.14159f * lineWidth); + this.pen_dx = new float[numPenSegments]; + this.pen_dy = new float[numPenSegments]; + this.penIncluded = new boolean[numPenSegments]; + this.join = new float[2*numPenSegments]; for (int i = 0; i < numPenSegments; i++) { - double r = lineWidth/2.0; - double theta = (double)i*2.0*Math.PI/numPenSegments; + double theta = (i * 2.0 * Math.PI)/numPenSegments; double cos = Math.cos(theta); double sin = Math.sin(theta); - pen_dx[i] = (int)(r*(dm00*cos + dm01*sin)); - pen_dy[i] = (int)(r*(dm10*cos + dm11*sin)); + pen_dx[i] = (float)(lineWidth2 * (m00*cos + m01*sin)); + pen_dy[i] = (float)(lineWidth2 * (m10*cos + m11*sin)); } prev = CLOSE; @@ -233,32 +172,31 @@ public class Stroker extends LineSink { lineToOrigin = false; } - private void computeOffset(int x0, int y0, int x1, int y1, int[] m) { - long lx = (long)x1 - (long)x0; - long ly = (long)y1 - (long)y0; + private void computeOffset(float x0, float y0, + float x1, float y1, float[] m) { + float lx = x1 - x0; + float ly = y1 - y0; - int dx, dy; + float dx, dy; if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) { - long ilen = PiscesMath.hypot(lx, ly); + float ilen = (float)Math.hypot(lx, ly); if (ilen == 0) { dx = dy = 0; } else { - dx = (int)( (ly*scaledLineWidth2)/ilen); - dy = (int)(-(lx*scaledLineWidth2)/ilen); + dx = (ly * scaledLineWidth2)/ilen; + dy = -(lx * scaledLineWidth2)/ilen; } } else { - double dlx = x1 - x0; - double dly = y1 - y0; - double det = (double)m00*m11 - (double)m01*m10; int sdet = (det > 0) ? 1 : -1; - double a = dly*m00 - dlx*m10; - double b = dly*m01 - dlx*m11; - double dh = PiscesMath.hypot(a, b); - double div = sdet*lineWidth2/(65536.0*dh); - double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11; - double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2; - dx = (int)(ddx*div); - dy = (int)(ddy*div); + float a = ly * m00 - lx * m10; + float b = ly * m01 - lx * m11; + float dh = (float)Math.hypot(a, b); + float div = sdet * lineWidth2/dh; + + float ddx = ly * m00_2_m01_2 - lx * m00_m10_m01_m11; + float ddy = ly * m00_m10_m01_m11 - lx * m10_2_m11_2; + dx = ddx*div; + dy = ddy*div; } m[0] = dx; @@ -267,58 +205,43 @@ public class Stroker extends LineSink { private void ensureCapacity(int newrindex) { if (reverse.length < newrindex) { - int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)]; - System.arraycopy(reverse, 0, tmp, 0, rindex); - this.reverse = tmp; + reverse = java.util.Arrays.copyOf(reverse, 6*reverse.length/5); } } - private boolean isCCW(int x0, int y0, - int x1, int y1, - int x2, int y2) { - int dx0 = x1 - x0; - int dy0 = y1 - y0; - int dx1 = x2 - x1; - int dy1 = y2 - y1; - return (long)dx0*dy1 < (long)dy0*dx1; + private boolean isCCW(float x0, float y0, + float x1, float y1, + float x2, float y2) { + return (x1 - x0) * (y2 - y1) < (y1 - y0) * (x2 - x1); } - private boolean side(int x, int y, int x0, int y0, int x1, int y1) { - long lx = x; - long ly = y; - long lx0 = x0; - long ly0 = y0; - long lx1 = x1; - long ly1 = y1; - - return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0; + private boolean side(float x, float y, + float x0, float y0, + float x1, float y1) { + return (y0 - y1)*x + (x1 - x0)*y + (x0*y1 - x1*y0) > 0; } - private int computeRoundJoin(int cx, int cy, - int xa, int ya, - int xb, int yb, + private int computeRoundJoin(float cx, float cy, + float xa, float ya, + float xb, float yb, int side, boolean flip, - int[] join) { - int px, py; + float[] join) { + float px, py; int ncoords = 0; boolean centerSide; if (side == 0) { centerSide = side(cx, cy, xa, ya, xb, yb); } else { - centerSide = (side == 1) ? true : false; + centerSide = (side == 1); } for (int i = 0; i < numPenSegments; i++) { px = cx + pen_dx[i]; py = cy + pen_dy[i]; boolean penSide = side(px, py, xa, ya, xb, yb); - if (penSide != centerSide) { - penIncluded[i] = true; - } else { - penIncluded[i] = false; - } + penIncluded[i] = (penSide != centerSide); } int start = -1, end = -1; @@ -338,10 +261,10 @@ public class Stroker extends LineSink { } if (start != -1 && end != -1) { - long dxa = cx + pen_dx[start] - xa; - long dya = cy + pen_dy[start] - ya; - long dxb = cx + pen_dx[start] - xb; - long dyb = cy + pen_dy[start] - yb; + float dxa = cx + pen_dx[start] - xa; + float dya = cy + pen_dy[start] - ya; + float dxb = cx + pen_dx[start] - xb; + float dyb = cy + pen_dy[start] - yb; boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb); int i = rev ? end : start; @@ -362,22 +285,25 @@ public class Stroker extends LineSink { return ncoords/2; } - private static final long ROUND_JOIN_THRESHOLD = 1000L; - private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L; + // pisces used to use fixed point arithmetic with 16 decimal digits. I + // didn't want to change the values of the constants below when I converted + // it to floating point, so that's why the divisions by 2^16 are there. + private static final float ROUND_JOIN_THRESHOLD = 1000/65536f; + private static final float ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000/65536f; - private void drawRoundJoin(int x, int y, - int omx, int omy, int mx, int my, + private void drawRoundJoin(float x, float y, + float omx, float omy, float mx, float my, int side, boolean flip, boolean rev, - long threshold) { + float threshold) { if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) { return; } - long domx = (long)omx - mx; - long domy = (long)omy - my; - long len = domx*domx + domy*domy; + float domx = omx - mx; + float domy = omy - my; + float len = domx*domx + domy*domy; if (len < threshold) { return; } @@ -389,10 +315,10 @@ public class Stroker extends LineSink { my = -my; } - int bx0 = x + omx; - int by0 = y + omy; - int bx1 = x + mx; - int by1 = y + my; + float bx0 = x + omx; + float by0 = y + omy; + float bx1 = x + mx; + float by1 = y + my; int npoints = computeRoundJoin(x, y, bx0, by0, bx1, by1, side, flip, @@ -404,40 +330,30 @@ public class Stroker extends LineSink { // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1) // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1] - private void computeMiter(int ix0, int iy0, int ix1, int iy1, - int ix0p, int iy0p, int ix1p, int iy1p, - int[] m) { - long x0 = ix0; - long y0 = iy0; - long x1 = ix1; - long y1 = iy1; + private void computeMiter(float x0, float y0, float x1, float y1, + float x0p, float y0p, float x1p, float y1p, + float[] m) { + float x10 = x1 - x0; + float y10 = y1 - y0; + float x10p = x1p - x0p; + float y10p = y1p - y0p; - long x0p = ix0p; - long y0p = iy0p; - long x1p = ix1p; - long y1p = iy1p; - - long x10 = x1 - x0; - long y10 = y1 - y0; - long x10p = x1p - x0p; - long y10p = y1p - y0p; - - long den = (x10*y10p - x10p*y10) >> 16; + float den = x10*y10p - x10p*y10; if (den == 0) { - m[0] = ix0; - m[1] = iy0; + m[0] = x0; + m[1] = y0; return; } - long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16; - m[0] = (int)(x0 + (t*x10)/den); - m[1] = (int)(y0 + (t*y10)/den); + float t = x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0); + m[0] = x0 + (t*x10)/den; + m[1] = y0 + (t*y10)/den; } - private void drawMiter(int px0, int py0, - int x0, int y0, - int x1, int y1, - int omx, int omy, int mx, int my, + private void drawMiter(float px0, float py0, + float x0, float y0, + float x1, float y1, + float omx, float omy, float mx, float my, boolean rev) { if (mx == omx && my == omy) { return; @@ -461,11 +377,11 @@ public class Stroker extends LineSink { miter); // Compute miter length in untransformed coordinates - long dx = (long)miter[0] - x0; - long dy = (long)miter[1] - y0; - long a = (dy*m00 - dx*m10) >> 16; - long b = (dy*m01 - dx*m11) >> 16; - long lenSq = a*a + b*b; + float dx = miter[0] - x0; + float dy = miter[1] - y0; + float a = dy*m00 - dx*m10; + float b = dy*m01 - dx*m11; + float lenSq = a*a + b*b; if (lenSq < miterLimitSq) { emitLineTo(miter[0], miter[1], rev); @@ -473,7 +389,7 @@ public class Stroker extends LineSink { } - public void moveTo(int x0, int y0) { + public void moveTo(float x0, float y0) { // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); if (lineToOrigin) { @@ -501,7 +417,7 @@ public class Stroker extends LineSink { this.joinSegment = true; } - public void lineTo(int x1, int y1) { + public void lineTo(float x1, float y1) { // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")"); if (lineToOrigin) { @@ -526,10 +442,10 @@ public class Stroker extends LineSink { joinSegment = false; } - private void lineToImpl(int x1, int y1, boolean joinSegment) { + private void lineToImpl(float x1, float y1, boolean joinSegment) { computeOffset(x0, y0, x1, y1, offset); - int mx = offset[0]; - int my = offset[1]; + float mx = offset[0]; + float my = offset[1]; if (!started) { emitMoveTo(x0 + mx, y0 + my); @@ -567,10 +483,6 @@ public class Stroker extends LineSink { emitLineTo(x0 - mx, y0 - my, true); emitLineTo(x1 - mx, y1 - my, true); - lx0 = x1 + mx; ly0 = y1 + my; - lx0p = x1 - mx; ly0p = y1 - my; - lx1 = x1; ly1 = y1; - this.omx = mx; this.omy = my; this.px0 = x0; @@ -594,8 +506,8 @@ public class Stroker extends LineSink { } computeOffset(x0, y0, sx0, sy0, offset); - int mx = offset[0]; - int my = offset[1]; + float mx = offset[0]; + float my = offset[1]; // Draw penultimate join boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0); @@ -678,12 +590,10 @@ public class Stroker extends LineSink { this.prev = MOVE_TO; } - long lineLength(long ldx, long ldy) { - long ldet = ((long)m00*m11 - (long)m01*m10) >> 16; - long la = ((long)ldy*m00 - (long)ldx*m10)/ldet; - long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet; - long llen = (int)PiscesMath.hypot(la, lb); - return llen; + double userSpaceLineLength(double dx, double dy) { + double a = (dy*m00 - dx*m10)/det; + double b = (dy*m01 - dx*m11)/det; + return Math.hypot(a, b); } private void finish() { @@ -692,13 +602,13 @@ public class Stroker extends LineSink { omx, omy, -omx, -omy, 1, false, false, ROUND_JOIN_THRESHOLD); } else if (capStyle == CAP_SQUARE) { - long ldx = (long)(px0 - x0); - long ldy = (long)(py0 - y0); - long llen = lineLength(ldx, ldy); - long s = (long)lineWidth2*65536/llen; + float dx = px0 - x0; + float dy = py0 - y0; + float len = (float)userSpaceLineLength(dx, dy); + float s = lineWidth2/len; - int capx = x0 - (int)(ldx*s >> 16); - int capy = y0 - (int)(ldy*s >> 16); + float capx = x0 - dx*s; + float capy = y0 - dy*s; emitLineTo(capx + omx, capy + omy); emitLineTo(capx - omx, capy - omy); @@ -714,13 +624,13 @@ public class Stroker extends LineSink { -mx0, -my0, mx0, my0, 1, false, false, ROUND_JOIN_THRESHOLD); } else if (capStyle == CAP_SQUARE) { - long ldx = (long)(sx1 - sx0); - long ldy = (long)(sy1 - sy0); - long llen = lineLength(ldx, ldy); - long s = (long)lineWidth2*65536/llen; + float dx = sx1 - sx0; + float dy = sy1 - sy0; + float len = (float)userSpaceLineLength(dx, dy); + float s = lineWidth2/len; - int capx = sx0 - (int)(ldx*s >> 16); - int capy = sy0 - (int)(ldy*s >> 16); + float capx = sx0 - dx*s; + float capy = sy0 - dy*s; emitLineTo(capx - mx0, capy - my0); emitLineTo(capx + mx0, capy + my0); @@ -730,17 +640,17 @@ public class Stroker extends LineSink { this.joinSegment = false; } - private void emitMoveTo(int x0, int y0) { + private void emitMoveTo(float x0, float y0) { // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); output.moveTo(x0, y0); } - private void emitLineTo(int x1, int y1) { + private void emitLineTo(float x1, float y1) { // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")"); output.lineTo(x1, y1); } - private void emitLineTo(int x1, int y1, boolean rev) { + private void emitLineTo(float x1, float y1, boolean rev) { if (rev) { ensureCapacity(rindex + 2); reverse[rindex++] = x1; @@ -755,3 +665,4 @@ public class Stroker extends LineSink { output.close(); } } + diff --git a/jdk/src/share/classes/sun/java2d/pisces/Transform4.java b/jdk/src/share/classes/sun/java2d/pisces/Transform4.java deleted file mode 100644 index bd79b762dba..00000000000 --- a/jdk/src/share/classes/sun/java2d/pisces/Transform4.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2007, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -package sun.java2d.pisces; - -public class Transform4 { - - public int m00, m01, m10, m11; -// double det; // det*65536 - - public Transform4() { - this(1 << 16, 0, 0, 1 << 16); - } - - public Transform4(int m00, int m01, - int m10, int m11) { - this.m00 = m00; - this.m01 = m01; - this.m10 = m10; - this.m11 = m11; - -// this.det = (double)m00*m11 - (double)m01*m10; - } - -// public Transform4 createInverse() { -// double dm00 = m00/65536.0; -// double dm01 = m01/65536.0; -// double dm10 = m10/65536.0; -// double dm11 = m11/65536.0; - -// double invdet = 65536.0/(dm00*dm11 - dm01*dm10); - -// int im00 = (int)( dm11*invdet); -// int im01 = (int)(-dm01*invdet); -// int im10 = (int)(-dm10*invdet); -// int im11 = (int)( dm00*invdet); - -// return new Transform4(im00, im01, im10, im11); -// } - -// public void transform(int[] point) { -// } - -// /** -// * Returns the length of the line segment obtained by inverse -// * transforming the points (x0, y0) and (x1, -// * y1). -// */ -// public int getTransformedLength(int x0, int x1, int y0, int y1) { -// int lx = x1 - x0; -// int ly = y1 - y0; - -// double a = (double)m00*ly - (double)m10*lx; -// double b = (double)m01*ly - (double)m11*lx; -// double len = PiscesMath.sqrt((a*a + b*b)/(det*det)); -// return (int)(len*65536.0); -// } - -// public int getType() { -// } - -} From 46e4eafc0cfbf646a0c3c6a0d6fa2b393b8d73a5 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Tue, 10 Aug 2010 13:15:40 -0700 Subject: [PATCH 10/32] 6923794: About 40 JCK test case fail with AssertionError if -esa option is specified Removed the assert Reviewed-by: alanb --- jdk/src/solaris/classes/java/io/UnixFileSystem.java | 1 - jdk/src/windows/classes/java/io/Win32FileSystem.java | 1 - 2 files changed, 2 deletions(-) diff --git a/jdk/src/solaris/classes/java/io/UnixFileSystem.java b/jdk/src/solaris/classes/java/io/UnixFileSystem.java index df070bd0039..2c0feb2d5ef 100644 --- a/jdk/src/solaris/classes/java/io/UnixFileSystem.java +++ b/jdk/src/solaris/classes/java/io/UnixFileSystem.java @@ -187,7 +187,6 @@ class UnixFileSystem extends FileSystem { } } } - assert canonicalize0(path).equals(res) || path.startsWith(javaHome); return res; } } diff --git a/jdk/src/windows/classes/java/io/Win32FileSystem.java b/jdk/src/windows/classes/java/io/Win32FileSystem.java index 510b84abf62..490c411b072 100644 --- a/jdk/src/windows/classes/java/io/Win32FileSystem.java +++ b/jdk/src/windows/classes/java/io/Win32FileSystem.java @@ -424,7 +424,6 @@ class Win32FileSystem extends FileSystem { } } } - assert canonicalize0(path).equalsIgnoreCase(res); return res; } } From a3c07597f250e90c84c8dd77ba61d622c4bfed9e Mon Sep 17 00:00:00 2001 From: Denis Lila Date: Wed, 11 Aug 2010 10:05:56 -0400 Subject: [PATCH 11/32] 6976265: No STROKE_CONTROL Implemented it in sun.java2d.pisces by adding a PathIterator. Reviewed-by: flar --- .../java2d/pisces/PiscesRenderingEngine.java | 150 +++++++++++++++++- 1 file changed, 142 insertions(+), 8 deletions(-) diff --git a/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java b/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java index 23ef19ee31f..ee2b35e6809 100644 --- a/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java +++ b/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java @@ -27,6 +27,7 @@ package sun.java2d.pisces; import java.awt.Shape; import java.awt.BasicStroke; +import java.awt.geom.FlatteningPathIterator; import java.awt.geom.Path2D; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; @@ -39,6 +40,8 @@ import sun.java2d.pipe.AATileGenerator; public class PiscesRenderingEngine extends RenderingEngine { public static double defaultFlat = 0.1; + private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA} + /** * Create a widened path as specified by the parameters. *

@@ -69,7 +72,7 @@ public class PiscesRenderingEngine extends RenderingEngine { strokeTo(src, null, width, - false, + NormMode.OFF, caps, join, miterlimit, @@ -127,7 +130,10 @@ public class PiscesRenderingEngine extends RenderingEngine { boolean antialias, final PathConsumer2D consumer) { - strokeTo(src, at, bs, thin, normalize, antialias, + NormMode norm = (normalize) ? + ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) + : NormMode.OFF; + strokeTo(src, at, bs, thin, norm, antialias, new LineSink() { public void moveTo(float x0, float y0) { consumer.moveTo(x0, y0); @@ -149,7 +155,7 @@ public class PiscesRenderingEngine extends RenderingEngine { AffineTransform at, BasicStroke bs, boolean thin, - boolean normalize, + NormMode normalize, boolean antialias, LineSink lsink) { @@ -244,7 +250,7 @@ public class PiscesRenderingEngine extends RenderingEngine { void strokeTo(Shape src, AffineTransform at, float width, - boolean normalize, + NormMode normalize, int caps, int join, float miterlimit, @@ -263,11 +269,130 @@ public class PiscesRenderingEngine extends RenderingEngine { if (dashes != null) { lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11); } - - PathIterator pi = src.getPathIterator(at, defaultFlat); + PathIterator pi; + if (normalize != NormMode.OFF) { + pi = new FlatteningPathIterator( + new NormalizingPathIterator(src.getPathIterator(at), normalize), + defaultFlat); + } else { + pi = src.getPathIterator(at, defaultFlat); + } pathTo(pi, lsink); } + private static class NormalizingPathIterator implements PathIterator { + + private final PathIterator src; + + // the adjustment applied to the current position. + private float curx_adjust, cury_adjust; + // the adjustment applied to the last moveTo position. + private float movx_adjust, movy_adjust; + + // constants used in normalization computations + private final float lval, rval; + + NormalizingPathIterator(PathIterator src, NormMode mode) { + this.src = src; + switch (mode) { + case ON_NO_AA: + // round to nearest (0.25, 0.25) pixel + lval = rval = 0.25f; + break; + case ON_WITH_AA: + // round to nearest pixel center + lval = 0f; + rval = 0.5f; + break; + case OFF: + throw new InternalError("A NormalizingPathIterator should " + + "not be created if no normalization is being done"); + default: + throw new InternalError("Unrecognized normalization mode"); + } + } + + public int currentSegment(float[] coords) { + int type = src.currentSegment(coords); + + int lastCoord; + switch(type) { + case PathIterator.SEG_CUBICTO: + lastCoord = 4; + break; + case PathIterator.SEG_QUADTO: + lastCoord = 2; + break; + case PathIterator.SEG_LINETO: + case PathIterator.SEG_MOVETO: + lastCoord = 0; + break; + case PathIterator.SEG_CLOSE: + // we don't want to deal with this case later. We just exit now + curx_adjust = movx_adjust; + cury_adjust = movy_adjust; + return type; + default: + throw new InternalError("Unrecognized curve type"); + } + + // normalize endpoint + float x_adjust = (float)Math.floor(coords[lastCoord] + lval) + rval - + coords[lastCoord]; + float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) + rval - + coords[lastCoord + 1]; + + coords[lastCoord ] += x_adjust; + coords[lastCoord + 1] += y_adjust; + + // now that the end points are done, normalize the control points + switch(type) { + case PathIterator.SEG_CUBICTO: + coords[0] += curx_adjust; + coords[1] += cury_adjust; + coords[2] += x_adjust; + coords[3] += y_adjust; + break; + case PathIterator.SEG_QUADTO: + coords[0] += (curx_adjust + x_adjust) / 2; + coords[1] += (cury_adjust + y_adjust) / 2; + break; + case PathIterator.SEG_LINETO: + break; + case PathIterator.SEG_MOVETO: + movx_adjust = x_adjust; + movy_adjust = y_adjust; + break; + case PathIterator.SEG_CLOSE: + throw new InternalError("This should be handled earlier."); + } + curx_adjust = x_adjust; + cury_adjust = y_adjust; + return type; + } + + public int currentSegment(double[] coords) { + float[] tmp = new float[6]; + int type = this.currentSegment(tmp); + for (int i = 0; i < 6; i++) { + coords[i] = (float) tmp[i]; + } + return type; + } + + public int getWindingRule() { + return src.getWindingRule(); + } + + public boolean isDone() { + return src.isDone(); + } + + public void next() { + src.next(); + } + } + void pathTo(PathIterator pi, LineSink lsink) { float coords[] = new float[2]; while (!pi.isDone()) { @@ -348,8 +473,16 @@ public class PiscesRenderingEngine extends RenderingEngine { { PiscesCache pc = PiscesCache.createInstance(); Renderer r; + NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; if (bs == null) { - PathIterator pi = s.getPathIterator(at, defaultFlat); + PathIterator pi; + if (normalize) { + pi = new FlatteningPathIterator( + new NormalizingPathIterator(s.getPathIterator(at), norm), + defaultFlat); + } else { + pi = s.getPathIterator(at, defaultFlat); + } r = new Renderer(3, 3, clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), @@ -360,7 +493,7 @@ public class PiscesRenderingEngine extends RenderingEngine { clip.getLoX(), clip.getLoY(), clip.getWidth(), clip.getHeight(), PathIterator.WIND_NON_ZERO, pc); - strokeTo(s, at, bs, thin, normalize, true, r); + strokeTo(s, at, bs, thin, norm, true, r); } r.endRendering(); PiscesTileGenerator ptg = new PiscesTileGenerator(pc, r.MAX_AA_ALPHA); @@ -391,3 +524,4 @@ public class PiscesRenderingEngine extends RenderingEngine { } } } + From 49d555edeeb77d020d4637a9dd088478229576f4 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Thu, 12 Aug 2010 19:53:25 +0100 Subject: [PATCH 12/32] 6971825: (so) improve scatter/gather implementation Reviewed-by: chegar, sherman --- .../sun/nio/ch/DatagramChannelImpl.java | 38 +-- .../classes/sun/nio/ch/FileChannelImpl.java | 35 +- jdk/src/share/classes/sun/nio/ch/IOUtil.java | 302 ++++++++---------- .../classes/sun/nio/ch/IOVecWrapper.java | 98 +++++- .../classes/sun/nio/ch/SocketChannelImpl.java | 38 +-- jdk/src/share/classes/sun/nio/ch/Util.java | 207 +++++++++--- 6 files changed, 411 insertions(+), 307 deletions(-) diff --git a/jdk/src/share/classes/sun/nio/ch/DatagramChannelImpl.java b/jdk/src/share/classes/sun/nio/ch/DatagramChannelImpl.java index 41b37c9e960..1d7d6758d8e 100644 --- a/jdk/src/share/classes/sun/nio/ch/DatagramChannelImpl.java +++ b/jdk/src/share/classes/sun/nio/ch/DatagramChannelImpl.java @@ -536,9 +536,11 @@ class DatagramChannelImpl } } - private long read0(ByteBuffer[] bufs) throws IOException { - if (bufs == null) - throw new NullPointerException(); + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) + throw new IndexOutOfBoundsException(); synchronized (readLock) { synchronized (stateLock) { ensureOpen(); @@ -552,7 +554,7 @@ class DatagramChannelImpl return 0; readerThread = NativeThread.current(); do { - n = IOUtil.read(fd, bufs, nd); + n = IOUtil.read(fd, dsts, offset, length, nd); } while ((n == IOStatus.INTERRUPTED) && isOpen()); return IOStatus.normalize(n); } finally { @@ -563,15 +565,6 @@ class DatagramChannelImpl } } - public long read(ByteBuffer[] dsts, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return read0(Util.subsequence(dsts, offset, length)); - } - public int write(ByteBuffer buf) throws IOException { if (buf == null) throw new NullPointerException(); @@ -599,9 +592,11 @@ class DatagramChannelImpl } } - private long write0(ByteBuffer[] bufs) throws IOException { - if (bufs == null) - throw new NullPointerException(); + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) + throw new IndexOutOfBoundsException(); synchronized (writeLock) { synchronized (stateLock) { ensureOpen(); @@ -615,7 +610,7 @@ class DatagramChannelImpl return 0; writerThread = NativeThread.current(); do { - n = IOUtil.write(fd, bufs, nd); + n = IOUtil.write(fd, srcs, offset, length, nd); } while ((n == IOStatus.INTERRUPTED) && isOpen()); return IOStatus.normalize(n); } finally { @@ -626,15 +621,6 @@ class DatagramChannelImpl } } - public long write(ByteBuffer[] srcs, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return write0(Util.subsequence(srcs, offset, length)); - } - protected void implConfigureBlocking(boolean block) throws IOException { IOUtil.configureBlocking(fd, block); } diff --git a/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java b/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java index 1bbd5e025ef..af732f5b3a3 100644 --- a/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java +++ b/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java @@ -143,7 +143,11 @@ public class FileChannelImpl } } - private long read0(ByteBuffer[] dsts) throws IOException { + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) + throw new IndexOutOfBoundsException(); ensureOpen(); if (!readable) throw new NonReadableChannelException(); @@ -156,7 +160,7 @@ public class FileChannelImpl if (!isOpen()) return 0; do { - n = IOUtil.read(fd, dsts, nd); + n = IOUtil.read(fd, dsts, offset, length, nd); } while ((n == IOStatus.INTERRUPTED) && isOpen()); return IOStatus.normalize(n); } finally { @@ -167,15 +171,6 @@ public class FileChannelImpl } } - public long read(ByteBuffer[] dsts, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return read0(Util.subsequence(dsts, offset, length)); - } - public int write(ByteBuffer src) throws IOException { ensureOpen(); if (!writable) @@ -200,7 +195,11 @@ public class FileChannelImpl } } - private long write0(ByteBuffer[] srcs) throws IOException { + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) + throw new IndexOutOfBoundsException(); ensureOpen(); if (!writable) throw new NonWritableChannelException(); @@ -213,7 +212,7 @@ public class FileChannelImpl if (!isOpen()) return 0; do { - n = IOUtil.write(fd, srcs, nd); + n = IOUtil.write(fd, srcs, offset, length, nd); } while ((n == IOStatus.INTERRUPTED) && isOpen()); return IOStatus.normalize(n); } finally { @@ -224,16 +223,6 @@ public class FileChannelImpl } } - public long write(ByteBuffer[] srcs, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return write0(Util.subsequence(srcs, offset, length)); - } - - // -- Other operations -- public long position() throws IOException { diff --git a/jdk/src/share/classes/sun/nio/ch/IOUtil.java b/jdk/src/share/classes/sun/nio/ch/IOUtil.java index 5b1dd95cddc..86acff63566 100644 --- a/jdk/src/share/classes/sun/nio/ch/IOUtil.java +++ b/jdk/src/share/classes/sun/nio/ch/IOUtil.java @@ -38,34 +38,6 @@ class IOUtil { private IOUtil() { } // No instantiation - /* - * Returns the index of first buffer in bufs with remaining, - * or -1 if there is nothing left - */ - private static int remaining(ByteBuffer[] bufs) { - int numBufs = bufs.length; - for (int i=0; i 0) - bufs = skipBufs(bufs, nextWithRemaining); + return write(fd, bufs, 0, bufs.length, nd); + } - int numBufs = bufs.length; + static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, + NativeDispatcher nd) + throws IOException + { + IOVecWrapper vec = IOVecWrapper.get(length); - // Create shadow to ensure DirectByteBuffers are used - ByteBuffer[] shadow = new ByteBuffer[numBufs]; + boolean completed = false; + int iov_len = 0; try { - for (int i=0; i 0) { + vec.setBuffer(iov_len, buf, pos, rem); + + // allocate shadow buffer to ensure I/O is done with direct buffer + if (!(buf instanceof DirectBuffer)) { + ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem); + shadow.put(buf); + shadow.flip(); + vec.setShadow(iov_len, shadow); + buf.position(pos); // temporarily restore position in user buffer + buf = shadow; + pos = shadow.position(); + } + + vec.putBase(iov_len, ((DirectBuffer)buf).address() + pos); + vec.putLen(iov_len, rem); + iov_len++; } } + if (iov_len == 0) + return 0L; - IOVecWrapper vec = null; - long bytesWritten = 0; - try { - // Create a native iovec array - vec= new IOVecWrapper(numBufs); - - // Fill in the iovec array with appropriate data - for (int i=0; i= len) { - bytesWritten -= len; - int newPosition = pos + len; - nextBuffer.position(newPosition); - } else { // Buffers not completely filled - if (bytesWritten > 0) { - assert(pos + bytesWritten < (long)Integer.MAX_VALUE); - int newPosition = (int)(pos + bytesWritten); - nextBuffer.position(newPosition); - } - break; + long left = bytesWritten; + for (int j=0; j 0) { + ByteBuffer buf = vec.getBuffer(j); + int pos = vec.getPosition(j); + int rem = vec.getRemaining(j); + int n = (left > rem) ? rem : (int)left; + buf.position(pos + n); + left -= n; } + // return shadow buffers to buffer pool + ByteBuffer shadow = vec.getShadow(j); + if (shadow != null) + Util.offerLastTemporaryDirectBuffer(shadow); + vec.clearRefs(j); } - return returnVal; + + completed = true; + return bytesWritten; + } finally { - // return any substituted buffers to cache - for (int i=0; i 0) - bufs = skipBufs(bufs, nextWithRemaining); + return read(fd, bufs, 0, bufs.length, nd); + } - int numBufs = bufs.length; + static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length, + NativeDispatcher nd) + throws IOException + { + IOVecWrapper vec = IOVecWrapper.get(length); - // Read into the shadow to ensure DirectByteBuffers are used - ByteBuffer[] shadow = new ByteBuffer[numBufs]; - boolean usingSlowBuffers = false; + boolean completed = false; + int iov_len = 0; try { - for (int i=0; i 0) { + vec.setBuffer(iov_len, buf, pos, rem); + + // allocate shadow buffer to ensure I/O is done with direct buffer + if (!(buf instanceof DirectBuffer)) { + ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem); + vec.setShadow(iov_len, shadow); + buf = shadow; + pos = shadow.position(); + } + + vec.putBase(iov_len, ((DirectBuffer)buf).address() + pos); + vec.putLen(iov_len, rem); + iov_len++; } } + if (iov_len == 0) + return 0L; - IOVecWrapper vec = null; - long bytesRead = 0; - try { - // Create a native iovec array - vec = new IOVecWrapper(numBufs); - - // Fill in the iovec array with appropriate data - for (int i=0; i= len) { - bytesRead -= len; - int newPosition = pos + len; - nextBuffer.position(newPosition); - } else { // Buffers not completely filled - if (bytesRead > 0) { - assert(pos + bytesRead < (long)Integer.MAX_VALUE); - int newPosition = (int)(pos + bytesRead); - nextBuffer.position(newPosition); + long left = bytesRead; + for (int j=0; j 0) { + ByteBuffer buf = vec.getBuffer(j); + int rem = vec.getRemaining(j); + int n = (left > rem) ? rem : (int)left; + if (shadow == null) { + int pos = vec.getPosition(j); + buf.position(pos + n); + } else { + shadow.limit(shadow.position() + n); + buf.put(shadow); } - break; + left -= n; } + if (shadow != null) + Util.offerLastTemporaryDirectBuffer(shadow); + vec.clearRefs(j); } - // Put results from shadow into the slow buffers - if (usingSlowBuffers) { - for (int i=0; i cached = + new ThreadLocal(); + + private IOVecWrapper(int size) { + this.size = size; + this.buf = new ByteBuffer[size]; + this.position = new int[size]; + this.remaining = new int[size]; + this.shadow = new ByteBuffer[size]; + this.vecArray = new AllocatedNativeObject(size * SIZE_IOVEC, false); + this.address = vecArray.address(); + } + + static IOVecWrapper get(int size) { + IOVecWrapper wrapper = cached.get(); + if (wrapper != null && wrapper.size < size) { + // not big enough; eagerly release memory + wrapper.vecArray.free(); + wrapper = null; + } + if (wrapper == null) { + wrapper = new IOVecWrapper(size); + Cleaner.create(wrapper, new Deallocator(wrapper.vecArray)); + cached.set(wrapper); + } + return wrapper; + } + + void setBuffer(int i, ByteBuffer buf, int pos, int rem) { + this.buf[i] = buf; + this.position[i] = pos; + this.remaining[i] = rem; + } + + void setShadow(int i, ByteBuffer buf) { + shadow[i] = buf; + } + + ByteBuffer getBuffer(int i) { + return buf[i]; + } + + int getPosition(int i) { + return position[i]; + } + + int getRemaining(int i) { + return remaining[i]; + } + + ByteBuffer getShadow(int i) { + return shadow[i]; + } + + void clearRefs(int i) { + buf[i] = null; + shadow[i] = null; } void putBase(int i, long base) { @@ -78,10 +154,6 @@ class IOVecWrapper { vecArray.putLong(offset, len); } - void free() { - vecArray.free(); - } - static { addressSize = Util.unsafe().addressSize(); LEN_OFFSET = addressSize; diff --git a/jdk/src/share/classes/sun/nio/ch/SocketChannelImpl.java b/jdk/src/share/classes/sun/nio/ch/SocketChannelImpl.java index ee84a0e626c..7d42d7d0096 100644 --- a/jdk/src/share/classes/sun/nio/ch/SocketChannelImpl.java +++ b/jdk/src/share/classes/sun/nio/ch/SocketChannelImpl.java @@ -385,9 +385,11 @@ class SocketChannelImpl } } - private long read0(ByteBuffer[] bufs) throws IOException { - if (bufs == null) - throw new NullPointerException(); + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) + throw new IndexOutOfBoundsException(); synchronized (readLock) { if (!ensureReadOpen()) return -1; @@ -401,7 +403,7 @@ class SocketChannelImpl } for (;;) { - n = IOUtil.read(fd, bufs, nd); + n = IOUtil.read(fd, dsts, offset, length, nd); if ((n == IOStatus.INTERRUPTED) && isOpen()) continue; return IOStatus.normalize(n); @@ -418,15 +420,6 @@ class SocketChannelImpl } } - public long read(ByteBuffer[] dsts, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return read0(Util.subsequence(dsts, offset, length)); - } - public int write(ByteBuffer buf) throws IOException { if (buf == null) throw new NullPointerException(); @@ -458,9 +451,11 @@ class SocketChannelImpl } } - public long write0(ByteBuffer[] bufs) throws IOException { - if (bufs == null) - throw new NullPointerException(); + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) + throw new IndexOutOfBoundsException(); synchronized (writeLock) { ensureWriteOpen(); long n = 0; @@ -472,7 +467,7 @@ class SocketChannelImpl writerThread = NativeThread.current(); } for (;;) { - n = IOUtil.write(fd, bufs, nd); + n = IOUtil.write(fd, srcs, offset, length, nd); if ((n == IOStatus.INTERRUPTED) && isOpen()) continue; return IOStatus.normalize(n); @@ -489,15 +484,6 @@ class SocketChannelImpl } } - public long write(ByteBuffer[] srcs, int offset, int length) - throws IOException - { - if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) - throw new IndexOutOfBoundsException(); - // ## Fix IOUtil.write so that we can avoid this array copy - return write0(Util.subsequence(srcs, offset, length)); - } - // package-private int sendOutOfBandData(byte b) throws IOException { synchronized (writeLock) { diff --git a/jdk/src/share/classes/sun/nio/ch/Util.java b/jdk/src/share/classes/sun/nio/ch/Util.java index b17801cbed7..50d280865cd 100644 --- a/jdk/src/share/classes/sun/nio/ch/Util.java +++ b/jdk/src/share/classes/sun/nio/ch/Util.java @@ -41,67 +41,180 @@ import sun.security.action.GetPropertyAction; class Util { - // -- Caches -- // The number of temp buffers in our pool - private static final int TEMP_BUF_POOL_SIZE = 3; + private static final int TEMP_BUF_POOL_SIZE = 8; - // Per-thread soft cache of the last temporary direct buffer - private static ThreadLocal>[] bufferPool; + // Per-thread cache of temporary direct buffers + private static ThreadLocal bufferCache = + new ThreadLocal() + { + @Override + protected BufferCache initialValue() { + return new BufferCache(); + } + }; - @SuppressWarnings("unchecked") - static ThreadLocal>[] createThreadLocalBufferPool() { - return new ThreadLocal[TEMP_BUF_POOL_SIZE]; - } - - static { - bufferPool = createThreadLocalBufferPool(); - for (int i=0; i>(); + /** + * A simple cache of direct buffers. + */ + private static class BufferCache { + // the array of buffers + private ByteBuffer[] buffers; + + // the number of buffers in the cache + private int count; + + // the index of the first valid buffer (undefined if count == 0) + private int start; + + private int next(int i) { + return (i + 1) % TEMP_BUF_POOL_SIZE; + } + + BufferCache() { + buffers = new ByteBuffer[TEMP_BUF_POOL_SIZE]; + } + + /** + * Removes and returns a buffer from the cache of at least the given + * size (or null if no suitable buffer is found). + */ + ByteBuffer get(int size) { + if (count == 0) + return null; // cache is empty + + ByteBuffer[] buffers = this.buffers; + + // search for suitable buffer (often the first buffer will do) + ByteBuffer buf = buffers[start]; + if (buf.capacity() < size) { + buf = null; + int i = start; + while ((i = next(i)) != start) { + ByteBuffer bb = buffers[i]; + if (bb == null) + break; + if (bb.capacity() >= size) { + buf = bb; + break; + } + } + if (buf == null) + return null; + // move first element to here to avoid re-packing + buffers[i] = buffers[start]; + } + + // remove first element + buffers[start] = null; + start = next(start); + count--; + + // prepare the buffer and return it + buf.rewind(); + buf.limit(size); + return buf; + } + + boolean offerFirst(ByteBuffer buf) { + if (count >= TEMP_BUF_POOL_SIZE) { + return false; + } else { + start = (start + TEMP_BUF_POOL_SIZE - 1) % TEMP_BUF_POOL_SIZE; + buffers[start] = buf; + count++; + return true; + } + } + + boolean offerLast(ByteBuffer buf) { + if (count >= TEMP_BUF_POOL_SIZE) { + return false; + } else { + int next = (start + count) % TEMP_BUF_POOL_SIZE; + buffers[next] = buf; + count++; + return true; + } + } + + boolean isEmpty() { + return count == 0; + } + + ByteBuffer removeFirst() { + assert count > 0; + ByteBuffer buf = buffers[start]; + buffers[start] = null; + start = next(start); + count--; + return buf; + } } + /** + * Returns a temporary buffer of at least the given size + */ static ByteBuffer getTemporaryDirectBuffer(int size) { - ByteBuffer buf = null; - // Grab a buffer if available - for (int i=0; i ref = bufferPool[i].get(); - if ((ref != null) && ((buf = ref.get()) != null) && - (buf.capacity() >= size)) { - buf.rewind(); - buf.limit(size); - bufferPool[i].set(null); - return buf; + BufferCache cache = bufferCache.get(); + ByteBuffer buf = cache.get(size); + if (buf != null) { + return buf; + } else { + // No suitable buffer in the cache so we need to allocate a new + // one. To avoid the cache growing then we remove the first + // buffer from the cache and free it. + if (!cache.isEmpty()) { + buf = cache.removeFirst(); + free(buf); } + return ByteBuffer.allocateDirect(size); } - - // Make a new one - return ByteBuffer.allocateDirect(size); } + /** + * Releases a temporary buffer by returning to the cache or freeing it. + */ static void releaseTemporaryDirectBuffer(ByteBuffer buf) { - if (buf == null) - return; - // Put it in an empty slot if such exists - for (int i=0; i ref = bufferPool[i].get(); - if ((ref == null) || (ref.get() == null)) { - bufferPool[i].set(new SoftReference(buf)); - return; - } - } - // Otherwise replace a smaller one in the cache if such exists - for (int i=0; i ref = bufferPool[i].get(); - ByteBuffer inCacheBuf = ref.get(); - if ((inCacheBuf == null) || (buf.capacity() > inCacheBuf.capacity())) { - bufferPool[i].set(new SoftReference(buf)); - return; - } - } + offerFirstTemporaryDirectBuffer(buf); + } - // release memory - ((DirectBuffer)buf).cleaner().clean(); + /** + * Releases a temporary buffer by returning to the cache or freeing it. If + * returning to the cache then insert it at the start so that it is + * likely to be returned by a subsequent call to getTemporaryDirectBuffer. + */ + static void offerFirstTemporaryDirectBuffer(ByteBuffer buf) { + assert buf != null; + BufferCache cache = bufferCache.get(); + if (!cache.offerFirst(buf)) { + // cache is full + free(buf); + } + } + + /** + * Releases a temporary buffer by returning to the cache or freeing it. If + * returning to the cache then insert it at the end. This makes it + * suitable for scatter/gather operations where the buffers are returned to + * cache in same order that they were obtained. + */ + static void offerLastTemporaryDirectBuffer(ByteBuffer buf) { + assert buf != null; + BufferCache cache = bufferCache.get(); + if (!cache.offerLast(buf)) { + // cache is full + free(buf); + } + } + + /** + * Frees the memory for the given direct buffer + */ + private static void free(ByteBuffer buf) { + ((DirectBuffer)buf).cleaner().clean(); } private static class SelectorWrapper { From abc4e4d7d03bd84a32e8030d78ebc1ee4cac73f5 Mon Sep 17 00:00:00 2001 From: Mandy Chung Date: Thu, 12 Aug 2010 16:36:49 -0700 Subject: [PATCH 13/32] 6973831: NPE when printing stack trace of OOME Initialize suppressedExceptions field to null Reviewed-by: briangoetz, dholmes, forax --- .../share/classes/java/lang/Throwable.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/jdk/src/share/classes/java/lang/Throwable.java b/jdk/src/share/classes/java/lang/Throwable.java index 213f5f86d5c..4d4169139e8 100644 --- a/jdk/src/share/classes/java/lang/Throwable.java +++ b/jdk/src/share/classes/java/lang/Throwable.java @@ -200,7 +200,16 @@ public class Throwable implements Serializable { * @serial * @since 1.7 */ - private List suppressedExceptions = Collections.emptyList(); + private List suppressedExceptions = null; + /* + * This field is lazily initialized when the first suppressed + * exception is added. + * + * OutOfMemoryError is preallocated in the VM for better OOM + * diagnosability during VM initialization. Constructor can't + * be not invoked. If a new field to be added in the future must + * be initialized to non-null, it requires a synchronized VM change. + */ /** Message for trying to suppress a null exception. */ private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception."; @@ -329,7 +338,7 @@ public class Throwable implements Serializable { * cause is nonexistent or unknown. * @since 1.4 */ - public Throwable getCause() { + public synchronized Throwable getCause() { return (cause==this ? null : cause); } @@ -563,7 +572,7 @@ public class Throwable implements Serializable { s.println("\tat " + traceElement); // Print suppressed exceptions, if any - for (Throwable se : suppressedExceptions) + for (Throwable se : getSuppressedExceptions()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu); // Print cause, if any @@ -604,7 +613,7 @@ public class Throwable implements Serializable { s.println(prefix + "\t... " + framesInCommon + " more"); // Print suppressed exceptions, if any - for (Throwable se : suppressedExceptions) + for (Throwable se : getSuppressedExceptions()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, prefix +"\t", dejaVu); @@ -747,7 +756,9 @@ public class Throwable implements Serializable { if (defensiveCopy[i] == null) throw new NullPointerException("stackTrace[" + i + "]"); - this.stackTrace = defensiveCopy; + synchronized (this) { + this.stackTrace = defensiveCopy; + } } /** @@ -772,11 +783,11 @@ public class Throwable implements Serializable { private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // read in all fields - List suppressed = Collections.emptyList(); + List suppressed = null; if (suppressedExceptions != null && !suppressedExceptions.isEmpty()) { // Copy Throwables to new list suppressed = new ArrayList(); - for(Throwable t : suppressedExceptions) { + for (Throwable t : suppressedExceptions) { if (t == null) throw new NullPointerException(NULL_CAUSE_MESSAGE); suppressed.add(t); @@ -819,7 +830,7 @@ public class Throwable implements Serializable { if (exception == this) throw new IllegalArgumentException("Self-suppression not permitted"); - if (suppressedExceptions.size() == 0) + if (suppressedExceptions == null) suppressedExceptions = new ArrayList(); suppressedExceptions.add(exception); } @@ -835,7 +846,10 @@ public class Throwable implements Serializable { * suppressed to deliver this exception. * @since 1.7 */ - public Throwable[] getSuppressedExceptions() { - return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY); + public synchronized Throwable[] getSuppressedExceptions() { + if (suppressedExceptions == null) + return EMPTY_THROWABLE_ARRAY; + else + return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY); } } From 5223492f6b1315267010f6c1fbf2faceaa189da8 Mon Sep 17 00:00:00 2001 From: Gary Benson Date: Fri, 13 Aug 2010 22:26:27 +0100 Subject: [PATCH 14/32] 6976186: Integrate Shark Shark is a JIT compiler for Zero that uses the LLVM compiler infrastructure. Reviewed-by: ohair --- jdk/make/jdk_generic_profile.sh | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/jdk/make/jdk_generic_profile.sh b/jdk/make/jdk_generic_profile.sh index 3f2e08446f0..4363a1b710e 100644 --- a/jdk/make/jdk_generic_profile.sh +++ b/jdk/make/jdk_generic_profile.sh @@ -340,6 +340,10 @@ PATH="${path4sdk}" export PATH # Export variables required for Zero +if [ "${SHARK_BUILD}" = true ] ; then + ZERO_BUILD=true + export ZERO_BUILD +fi if [ "${ZERO_BUILD}" = true ] ; then # ZERO_LIBARCH is the name of the architecture-specific # subdirectory under $JAVA_HOME/jre/lib @@ -417,4 +421,55 @@ if [ "${ZERO_BUILD}" = true ] ; then fi export LIBFFI_CFLAGS export LIBFFI_LIBS + + # LLVM_CFLAGS, LLVM_LDFLAGS and LLVM_LIBS tell the compiler how to + # compile and link against LLVM + if [ "${SHARK_BUILD}" = true ] ; then + if [ "${LLVM_CONFIG}" = "" ] ; then + LLVM_CONFIG=$(which llvm-config 2>/dev/null) + fi + if [ ! -x "${LLVM_CONFIG}" ] ; then + echo "ERROR: Unable to locate llvm-config" + exit 1 + fi + llvm_components="jit engine nativecodegen" + + unset LLVM_CFLAGS + for flag in $("${LLVM_CONFIG}" --cxxflags $llvm_components); do + if echo "${flag}" | grep -q '^-[ID]'; then + if [ "${flag}" != "-D_DEBUG" ] ; then + if [ "${LLVM_CFLAGS}" != "" ] ; then + LLVM_CFLAGS="${LLVM_CFLAGS} " + fi + LLVM_CFLAGS="${LLVM_CFLAGS}${flag}" + fi + fi + done + llvm_version=$("${LLVM_CONFIG}" --version | sed 's/\.//; s/svn.*//') + LLVM_CFLAGS="${LLVM_CFLAGS} -DSHARK_LLVM_VERSION=${llvm_version}" + + unset LLVM_LDFLAGS + for flag in $("${LLVM_CONFIG}" --ldflags $llvm_components); do + if echo "${flag}" | grep -q '^-L'; then + if [ "${LLVM_LDFLAGS}" != "" ] ; then + LLVM_LDFLAGS="${LLVM_LDFLAGS} " + fi + LLVM_LDFLAGS="${LLVM_LDFLAGS}${flag}" + fi + done + + unset LLVM_LIBS + for flag in $("${LLVM_CONFIG}" --libs $llvm_components); do + if echo "${flag}" | grep -q '^-l'; then + if [ "${LLVM_LIBS}" != "" ] ; then + LLVM_LIBS="${LLVM_LIBS} " + fi + LLVM_LIBS="${LLVM_LIBS}${flag}" + fi + done + + export LLVM_CFLAGS + export LLVM_LDFLAGS + export LLVM_LIBS + fi fi From a33a3ae92b5e30ad86474dba4f9d355d09b74c0e Mon Sep 17 00:00:00 2001 From: Mandy Chung Date: Mon, 16 Aug 2010 15:36:13 -0700 Subject: [PATCH 15/32] 6921234: TEST_BUG: java/lang/ClassLoader/deadlock/TestCrossDelegate.sh needs to be modified for Cygwin Add check for CYGWIN Reviewed-by: ohair --- jdk/test/java/lang/ClassLoader/deadlock/TestCrossDelegate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/test/java/lang/ClassLoader/deadlock/TestCrossDelegate.sh b/jdk/test/java/lang/ClassLoader/deadlock/TestCrossDelegate.sh index 47367f7de82..71e07441ce2 100644 --- a/jdk/test/java/lang/ClassLoader/deadlock/TestCrossDelegate.sh +++ b/jdk/test/java/lang/ClassLoader/deadlock/TestCrossDelegate.sh @@ -55,7 +55,7 @@ case "$OS" in Linux ) FS="/" ;; - Windows* ) + Windows* | CYGWIN* ) FS="\\" ;; esac From 8eca7db3071b0826a1198ea980265fe964e846f2 Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Tue, 17 Aug 2010 14:49:01 +0100 Subject: [PATCH 16/32] 6339649: URI.create should include a detail message when throwing IllegalArgumentException Create enclosing exception with message of enclosed Reviewed-by: alanb, chegar --- jdk/src/share/classes/java/net/URI.java | 4 +--- jdk/test/java/net/URI/Test.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/jdk/src/share/classes/java/net/URI.java b/jdk/src/share/classes/java/net/URI.java index c05b7b2abaa..9b891b4b4dd 100644 --- a/jdk/src/share/classes/java/net/URI.java +++ b/jdk/src/share/classes/java/net/URI.java @@ -856,9 +856,7 @@ public final class URI try { return new URI(str); } catch (URISyntaxException x) { - IllegalArgumentException y = new IllegalArgumentException(); - y.initCause(x); - throw y; + throw new IllegalArgumentException(x.getMessage(), x); } } diff --git a/jdk/test/java/net/URI/Test.java b/jdk/test/java/net/URI/Test.java index 449f7d1c19e..39b5b9487d1 100644 --- a/jdk/test/java/net/URI/Test.java +++ b/jdk/test/java/net/URI/Test.java @@ -1536,6 +1536,7 @@ public class Test { serial(); urls(); npes(); + bugs(); } @@ -1572,6 +1573,19 @@ public class Test { } + // miscellaneous bugs/rfes that don't fit in with the test framework + + static void bugs() { + // 6339649 - include detail message from nested exception + try { + URI uri = URI.create("http://nowhere.net/should not be permitted"); + } catch (IllegalArgumentException e) { + if ("".equals(e.getMessage()) || e.getMessage() == null) { + throw new RuntimeException ("No detail message"); + } + } + } + public static void main(String[] args) throws Exception { switch (args.length) { From 51e553fe0e6c1ad739115d13cab6cdfc8ba9151c Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Tue, 17 Aug 2010 16:01:54 -0700 Subject: [PATCH 17/32] 6969651: TEST_BUG: tools/jar/JarEntryTime.java failed on JDK7 when run on NFS Changed to use more appropriate nfs file time Reviewed-by: martin --- jdk/test/tools/jar/JarEntryTime.java | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/jdk/test/tools/jar/JarEntryTime.java b/jdk/test/tools/jar/JarEntryTime.java index 525d6b9032d..2998b29bc8a 100644 --- a/jdk/test/tools/jar/JarEntryTime.java +++ b/jdk/test/tools/jar/JarEntryTime.java @@ -23,7 +23,7 @@ /** * @test - * @bug 4225317 + * @bug 4225317 6969651 * @summary Check extracted files have date as per those in the .jar file */ @@ -68,17 +68,9 @@ public class JarEntryTime { } public static void realMain(String[] args) throws Throwable { - final long now = System.currentTimeMillis(); - final long earlier = now - (60L * 60L * 6L * 1000L); - final long yesterday = now - (60L * 60L * 24L * 1000L); - - // ZipEntry's mod date has 2 seconds precision: give extra time to - // allow for e.g. rounding/truncation and networked/samba drives. - final long PRECISION = 10000L; File dirOuter = new File("outer"); File dirInner = new File(dirOuter, "inner"); - File jarFile = new File("JarEntryTime.jar"); // Remove any leftovers from prior run @@ -99,6 +91,17 @@ public class JarEntryTime { PrintWriter pw = new PrintWriter(fileInner); pw.println("hello, world"); pw.close(); + + // Get the "now" from the "last-modified-time" of the last file we + // just created, instead of the "System.currentTimeMillis()", to + // workaround the possible "time difference" due to nfs. + final long now = fileInner.lastModified(); + final long earlier = now - (60L * 60L * 6L * 1000L); + final long yesterday = now - (60L * 60L * 24L * 1000L); + // ZipEntry's mod date has 2 seconds precision: give extra time to + // allow for e.g. rounding/truncation and networked/samba drives. + final long PRECISION = 10000L; + dirOuter.setLastModified(now); dirInner.setLastModified(yesterday); fileInner.setLastModified(earlier); From 737e6157fe0af6731d9329d8d85cb015e40e9d14 Mon Sep 17 00:00:00 2001 From: Kelly O'Hair Date: Wed, 18 Aug 2010 13:46:02 -0700 Subject: [PATCH 18/32] 6974005: Use of cygpath in Makefile logic needs to silence error messages Reviewed-by: mchung --- jdk/make/common/shared/Defs-windows.gmk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdk/make/common/shared/Defs-windows.gmk b/jdk/make/common/shared/Defs-windows.gmk index 121cd94ea49..ac9daf4cd6c 100644 --- a/jdk/make/common/shared/Defs-windows.gmk +++ b/jdk/make/common/shared/Defs-windows.gmk @@ -89,7 +89,7 @@ define FullPath $(shell $(CYGPATH_CMD) $1 2> $(DEV_NULL)) endef define OptFullPath -$(shell if [ "$1" != "" -a -d "$1" ]; then $(CYGPATH_CMD) "$1"; else echo "$1"; fi) +$(shell if [ "$1" != "" -a -d "$1" ]; then $(CYGPATH_CMD) "$1" 2> $(DEV_NULL); else echo "$1"; fi) endef else # Temporary until we upgrade to MKS 8.7, MKS pwd returns mixed mode path From fb2ceb5470e58bf186d65f0fa92975152acf2ecd Mon Sep 17 00:00:00 2001 From: Kelly O'Hair Date: Wed, 18 Aug 2010 13:46:39 -0700 Subject: [PATCH 19/32] 6932743: Makefiles not parsing version strings with - from uname -r Reviewed-by: mchung --- jdk/make/common/shared/Defs.gmk | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/jdk/make/common/shared/Defs.gmk b/jdk/make/common/shared/Defs.gmk index 3ccab90196c..ed32779c8c2 100644 --- a/jdk/make/common/shared/Defs.gmk +++ b/jdk/make/common/shared/Defs.gmk @@ -136,15 +136,20 @@ define GetVersion $(shell echo $1 | sed -e 's@[^0-9]*\([0-9][0-9]*\.[0-9][.0-9]*\).*@\1@' ) endef +# Return one part of the version numbers, watch out for non digits. +define VersionWord # Number Version +$(word $1,$(subst ., ,$(subst -, ,$2))) +endef + # Given a major.minor.micro version, return the major, minor, or micro number define MajorVersion -$(if $(word 1, $(subst ., ,$1)),$(word 1, $(subst ., ,$1)),0) +$(if $(call VersionWord,1,$1),$(call VersionWord,1,$1),0) endef define MinorVersion -$(if $(word 2, $(subst ., ,$1)),$(word 2, $(subst ., ,$1)),0) +$(if $(call VersionWord,2,$1),$(call VersionWord,2,$1),0) endef define MicroVersion -$(if $(word 3, $(subst ., ,$1)),$(word 3, $(subst ., ,$1)),0) +$(if $(call VersionWord,3,$1),$(call VersionWord,3,$1),0) endef # Macro that returns missing, same, newer, or older $1=version $2=required From 34198dbc4c7ebfe029baba07bfd696935095ac91 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 19 Aug 2010 11:26:32 +0800 Subject: [PATCH 20/32] 6976536: Solaris JREs do not have the krb5.kdc.bad.policy configured by default Reviewed-by: valeriep --- .../share/lib/security/java.security-solaris | 27 +++++++++++++ .../share/lib/security/java.security-windows | 27 +++++++++++++ .../sun/security/krb5/BadKdcDefaultValue.java | 40 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 jdk/test/sun/security/krb5/BadKdcDefaultValue.java diff --git a/jdk/src/share/lib/security/java.security-solaris b/jdk/src/share/lib/security/java.security-solaris index 1e447b7c24d..43621a48511 100644 --- a/jdk/src/share/lib/security/java.security-solaris +++ b/jdk/src/share/lib/security/java.security-solaris @@ -260,3 +260,30 @@ networkaddress.cache.negative.ttl=10 # Example, # ocsp.responderCertSerialNumber=2A:FF:00 +# +# Policy for failed Kerberos KDC lookups: +# +# When a KDC is unavailable (network error, service failure, etc), it is +# put inside a blacklist and accessed less often for future requests. The +# value (case-insensitive) for this policy can be: +# +# tryLast +# KDCs in the blacklist are always tried after those not on the list. +# +# tryLess[:max_retries,timeout] +# KDCs in the blacklist are still tried by their order in the configuration, +# but with smaller max_retries and timeout values. max_retries and timeout +# are optional numerical parameters (default 1 and 5000, which means once +# and 5 seconds). Please notes that if any of the values defined here is +# more than what is defined in krb5.conf, it will be ignored. +# +# Whenever a KDC is detected as available, it is removed from the blacklist. +# The blacklist is reset when krb5.conf is reloaded. You can add +# refreshKrb5Config=true to a JAAS configuration file so that krb5.conf is +# reloaded whenever a JAAS authentication is attempted. +# +# Example, +# krb5.kdc.bad.policy = tryLast +# krb5.kdc.bad.policy = tryLess:2,2000 +krb5.kdc.bad.policy = tryLast + diff --git a/jdk/src/share/lib/security/java.security-windows b/jdk/src/share/lib/security/java.security-windows index c94c76b7ffb..21573b7d23b 100644 --- a/jdk/src/share/lib/security/java.security-windows +++ b/jdk/src/share/lib/security/java.security-windows @@ -260,3 +260,30 @@ networkaddress.cache.negative.ttl=10 # Example, # ocsp.responderCertSerialNumber=2A:FF:00 +# +# Policy for failed Kerberos KDC lookups: +# +# When a KDC is unavailable (network error, service failure, etc), it is +# put inside a blacklist and accessed less often for future requests. The +# value (case-insensitive) for this policy can be: +# +# tryLast +# KDCs in the blacklist are always tried after those not on the list. +# +# tryLess[:max_retries,timeout] +# KDCs in the blacklist are still tried by their order in the configuration, +# but with smaller max_retries and timeout values. max_retries and timeout +# are optional numerical parameters (default 1 and 5000, which means once +# and 5 seconds). Please notes that if any of the values defined here is +# more than what is defined in krb5.conf, it will be ignored. +# +# Whenever a KDC is detected as available, it is removed from the blacklist. +# The blacklist is reset when krb5.conf is reloaded. You can add +# refreshKrb5Config=true to a JAAS configuration file so that krb5.conf is +# reloaded whenever a JAAS authentication is attempted. +# +# Example, +# krb5.kdc.bad.policy = tryLast +# krb5.kdc.bad.policy = tryLess:2,2000 +krb5.kdc.bad.policy = tryLast + diff --git a/jdk/test/sun/security/krb5/BadKdcDefaultValue.java b/jdk/test/sun/security/krb5/BadKdcDefaultValue.java new file mode 100644 index 00000000000..1235cce4b9d --- /dev/null +++ b/jdk/test/sun/security/krb5/BadKdcDefaultValue.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010, 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. + */ +/* + * @test + * @bug 6976536 + * @summary Solaris JREs do not have the krb5.kdc.bad.policy configured by default. + * @run main/othervm BadKdcDefaultValue + */ + +import java.security.Security; + +public class BadKdcDefaultValue { + public static void main(String[] args) throws Exception { + if (!"tryLast".equalsIgnoreCase( + Security.getProperty("krb5.kdc.bad.policy"))) { + throw new Exception("Default value not correct"); + } + } +} + From c736a3d9c7bf0695e7fd2d95e0ad3c651ed6cc41 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 19 Aug 2010 12:24:53 +0800 Subject: [PATCH 21/32] 6921610: 1.6 update 17 and 18 throw java.lang.IndexOutOfBoundsException Reviewed-by: vinnie, xuelei --- jdk/src/share/classes/com/sun/jndi/ldap/Connection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jdk/src/share/classes/com/sun/jndi/ldap/Connection.java b/jdk/src/share/classes/com/sun/jndi/ldap/Connection.java index 4503d261fff..dec38eb752d 100644 --- a/jdk/src/share/classes/com/sun/jndi/ldap/Connection.java +++ b/jdk/src/share/classes/com/sun/jndi/ldap/Connection.java @@ -813,7 +813,8 @@ public final class Connection implements Runnable { try { while (true) { try { - inbuf = new byte[10]; + // type and length (at most 128 octets for long form) + inbuf = new byte[129]; offset = 0; seqlen = 0; From facee61c79411b148ee45d737d268e868e6af1b3 Mon Sep 17 00:00:00 2001 From: Kumar Srinivasan Date: Thu, 19 Aug 2010 14:08:04 -0700 Subject: [PATCH 22/32] 6888127: java.util.jar.Pack200.Packer Memory Leak Reviewed-by: jrose --- .../com/sun/java/util/jar/pack/Attribute.java | 100 ++++++------- .../sun/java/util/jar/pack/ConstantPool.java | 113 +++++++------- .../com/sun/java/util/jar/pack/Driver.java | 23 +-- .../sun/java/util/jar/pack/NativeUnpack.java | 11 +- .../com/sun/java/util/jar/pack/Package.java | 122 ++++++++------- .../sun/java/util/jar/pack/PackerImpl.java | 140 ++++++++---------- .../com/sun/java/util/jar/pack/TLGlobals.java | 97 ++++++++++++ .../sun/java/util/jar/pack/UnpackerImpl.java | 67 ++++----- .../com/sun/java/util/jar/pack/Utils.java | 48 +++++- 9 files changed, 420 insertions(+), 301 deletions(-) create mode 100644 jdk/src/share/classes/com/sun/java/util/jar/pack/TLGlobals.java diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/Attribute.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/Attribute.java index 515218729ed..0c1b0954300 100644 --- a/jdk/src/share/classes/com/sun/java/util/jar/pack/Attribute.java +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/Attribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -27,7 +27,6 @@ package com.sun.java.util.jar.pack; import java.io.*; import java.util.*; -import com.sun.java.util.jar.pack.Package.Class; import com.sun.java.util.jar.pack.ConstantPool.*; /** @@ -96,20 +95,20 @@ class Attribute implements Comparable, Constants { return this.def.compareTo(that.def); } - static private final byte[] noBytes = {}; - static private final HashMap canonLists = new HashMap(); - static private final HashMap attributes = new HashMap(); - static private final HashMap standardDefs = new HashMap(); + private static final byte[] noBytes = {}; + private static final Map, List> canonLists = new HashMap<>(); + private static final Map attributes = new HashMap<>(); + private static final Map standardDefs = new HashMap<>(); // Canonicalized lists of trivial attrs (Deprecated, etc.) // are used by trimToSize, in order to reduce footprint // of some common cases. (Note that Code attributes are // always zero size.) - public static List getCanonList(List al) { + public static List getCanonList(List al) { synchronized (canonLists) { - List cl = (List) canonLists.get(al); + List cl = canonLists.get(al); if (cl == null) { - cl = new ArrayList(al.size()); + cl = new ArrayList<>(al.size()); cl.addAll(al); cl = Collections.unmodifiableList(cl); canonLists.put(al, cl); @@ -122,7 +121,7 @@ class Attribute implements Comparable, Constants { public static Attribute find(int ctype, String name, String layout) { Layout key = Layout.makeKey(ctype, name, layout); synchronized (attributes) { - Attribute a = (Attribute) attributes.get(key); + Attribute a = attributes.get(key); if (a == null) { a = new Layout(ctype, name, layout).canonicalInstance(); attributes.put(key, a); @@ -131,24 +130,29 @@ class Attribute implements Comparable, Constants { } } - public static Object keyForLookup(int ctype, String name) { + public static Layout keyForLookup(int ctype, String name) { return Layout.makeKey(ctype, name); } // Find canonical empty attribute with given ctype and name, // and with the standard layout. - public static Attribute lookup(Map defs, int ctype, String name) { - if (defs == null) defs = standardDefs; - return (Attribute) defs.get(Layout.makeKey(ctype, name)); + public static Attribute lookup(Map defs, int ctype, + String name) { + if (defs == null) { + defs = standardDefs; + } + return defs.get(Layout.makeKey(ctype, name)); } - public static Attribute define(Map defs, int ctype, String name, String layout) { + + public static Attribute define(Map defs, int ctype, + String name, String layout) { Attribute a = find(ctype, name, layout); defs.put(Layout.makeKey(ctype, name), a); return a; } static { - Map sd = standardDefs; + Map sd = standardDefs; define(sd, ATTR_CONTEXT_CLASS, "Signature", "RSH"); define(sd, ATTR_CONTEXT_CLASS, "Synthetic", ""); define(sd, ATTR_CONTEXT_CLASS, "Deprecated", ""); @@ -244,7 +248,7 @@ class Attribute implements Comparable, Constants { +"\n ()[] ]" ) }; - Map sd = standardDefs; + Map sd = standardDefs; String defaultLayout = mdLayouts[2]; String annotationsLayout = mdLayouts[1] + mdLayouts[2]; String paramsLayout = mdLayouts[0] + annotationsLayout; @@ -275,10 +279,6 @@ class Attribute implements Comparable, Constants { return null; } - public static Map getStandardDefs() { - return new HashMap(standardDefs); - } - /** Base class for any attributed object (Class, Field, Method, Code). * Flags are included because they are used to help transmit the * presence of attributes. That is, flags are a mix of modifier @@ -291,7 +291,7 @@ class Attribute implements Comparable, Constants { protected abstract Entry[] getCPMap(); protected int flags; // defined here for convenience - protected List attributes; + protected List attributes; public int attributeSize() { return (attributes == null) ? 0 : attributes.size(); @@ -301,16 +301,15 @@ class Attribute implements Comparable, Constants { if (attributes == null) { return; } - if (attributes.size() == 0) { + if (attributes.isEmpty()) { attributes = null; return; } if (attributes instanceof ArrayList) { - ArrayList al = (ArrayList) attributes; + ArrayList al = (ArrayList)attributes; al.trimToSize(); boolean allCanon = true; - for (Iterator i = al.iterator(); i.hasNext(); ) { - Attribute a = (Attribute) i.next(); + for (Attribute a : al) { if (!a.isCanonical()) { allCanon = false; } @@ -330,9 +329,9 @@ class Attribute implements Comparable, Constants { public void addAttribute(Attribute a) { if (attributes == null) - attributes = new ArrayList(3); + attributes = new ArrayList<>(3); else if (!(attributes instanceof ArrayList)) - attributes = new ArrayList(attributes); // unfreeze it + attributes = new ArrayList<>(attributes); // unfreeze it attributes.add(a); } @@ -340,32 +339,31 @@ class Attribute implements Comparable, Constants { if (attributes == null) return null; if (!attributes.contains(a)) return null; if (!(attributes instanceof ArrayList)) - attributes = new ArrayList(attributes); // unfreeze it + attributes = new ArrayList<>(attributes); // unfreeze it attributes.remove(a); return a; } public Attribute getAttribute(int n) { - return (Attribute) attributes.get(n); + return attributes.get(n); } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { if (attributes == null) return; - for (Iterator i = attributes.iterator(); i.hasNext(); ) { - Attribute a = (Attribute) i.next(); + for (Attribute a : attributes) { a.visitRefs(this, mode, refs); } } - static final List noAttributes = Arrays.asList(new Object[0]); + static final List noAttributes = Arrays.asList(new Attribute[0]); - public List getAttributes() { + public List getAttributes() { if (attributes == null) return noAttributes; return attributes; } - public void setAttributes(List attrList) { + public void setAttributes(List attrList) { if (attrList.isEmpty()) attributes = null; else @@ -374,8 +372,7 @@ class Attribute implements Comparable, Constants { public Attribute getAttribute(String attrName) { if (attributes == null) return null; - for (Iterator i = attributes.iterator(); i.hasNext(); ) { - Attribute a = (Attribute) i.next(); + for (Attribute a : attributes) { if (a.name().equals(attrName)) return a; } @@ -384,8 +381,7 @@ class Attribute implements Comparable, Constants { public Attribute getAttribute(Layout attrDef) { if (attributes == null) return null; - for (Iterator i = attributes.iterator(); i.hasNext(); ) { - Attribute a = (Attribute) i.next(); + for (Attribute a : attributes) { if (a.layout() == attrDef) return a; } @@ -457,14 +453,8 @@ class Attribute implements Comparable, Constants { public String layout() { return layout; } public Attribute canonicalInstance() { return canon; } - // Cache of name reference. - private Entry nameRef; // name, for use by visitRefs public Entry getNameRef() { - Entry nameRef = this.nameRef; - if (nameRef == null) { - this.nameRef = nameRef = ConstantPool.getUtf8Entry(name()); - } - return nameRef; + return ConstantPool.getUtf8Entry(name()); } public boolean isEmpty() { return layout == ""; } @@ -834,14 +824,14 @@ class Attribute implements Comparable, Constants { */ static //private Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) { - ArrayList col = new ArrayList(layout.length()); + ArrayList col = new ArrayList<>(layout.length()); tokenizeLayout(self, curCble, layout, col); Layout.Element[] res = new Layout.Element[col.size()]; col.toArray(res); return res; } static //private - void tokenizeLayout(Layout self, int curCble, String layout, ArrayList col) { + void tokenizeLayout(Layout self, int curCble, String layout, ArrayList col) { boolean prevBCI = false; for (int len = layout.length(), i = 0; i < len; ) { int start = i; @@ -899,7 +889,7 @@ class Attribute implements Comparable, Constants { case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' kind = EK_UN; i = tokenizeSInt(e, layout, i); - ArrayList cases = new ArrayList(); + ArrayList cases = new ArrayList<>(); for (;;) { // Keep parsing cases until we hit the default case. if (layout.charAt(i++) != '(') @@ -1053,7 +1043,7 @@ class Attribute implements Comparable, Constants { } static //private String[] splitBodies(String layout) { - ArrayList bodies = new ArrayList(); + ArrayList bodies = new ArrayList<>(); // Parse several independent layout bodies: "[foo][bar]...[baz]" for (int i = 0; i < layout.length(); i++) { if (layout.charAt(i++) != '[') @@ -1132,7 +1122,9 @@ class Attribute implements Comparable, Constants { int parseIntBefore(String layout, int dash) { int end = dash; int beg = end; - while (beg > 0 && isDigit(layout.charAt(beg-1))) --beg; + while (beg > 0 && isDigit(layout.charAt(beg-1))) { + --beg; + } if (beg == end) return Integer.parseInt("empty"); // skip backward over a sign if (beg >= 1 && layout.charAt(beg-1) == '-') --beg; @@ -1145,7 +1137,9 @@ class Attribute implements Comparable, Constants { int end = beg; int limit = layout.length(); if (end < limit && layout.charAt(end) == '-') ++end; - while (end < limit && isDigit(layout.charAt(end))) ++end; + while (end < limit && isDigit(layout.charAt(end))) { + ++end; + } if (beg == end) return Integer.parseInt("empty"); return Integer.parseInt(layout.substring(beg, end)); } diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/ConstantPool.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/ConstantPool.java index ab138bc2d91..ac6199abe25 100644 --- a/jdk/src/share/classes/com/sun/java/util/jar/pack/ConstantPool.java +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/ConstantPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2010, 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 @@ -25,7 +25,6 @@ package com.sun.java.util.jar.pack; -import java.io.*; import java.util.*; /** @@ -40,20 +39,13 @@ class ConstantPool implements Constants { return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE); } - // Uniquification tables for factory methods: - private static final HashMap utf8Entries = new HashMap(); - private static final HashMap classEntries = new HashMap(); - private static final HashMap literalEntries = new HashMap(); - private static final HashMap signatureEntries = new HashMap(); - private static final HashMap descriptorEntries = new HashMap(); - private static final HashMap memberEntries = new HashMap(); - /** Factory for Utf8 string constants. * Used for well-known strings like "SourceFile", "", etc. * Also used to back up more complex constant pool entries, like Class. */ public static synchronized Utf8Entry getUtf8Entry(String value) { - Utf8Entry e = (Utf8Entry) utf8Entries.get(value); + Map utf8Entries = Utils.getUtf8Entries(); + Utf8Entry e = utf8Entries.get(value); if (e == null) { e = new Utf8Entry(value); utf8Entries.put(e.stringValue(), e); @@ -62,9 +54,10 @@ class ConstantPool implements Constants { } /** Factory for Class constants. */ public static synchronized ClassEntry getClassEntry(String name) { - ClassEntry e = (ClassEntry) classEntries.get(name); + Map classEntries = Utils.getClassEntries(); + ClassEntry e = classEntries.get(name); if (e == null) { - e = (ClassEntry) new ClassEntry(getUtf8Entry(name)); + e = new ClassEntry(getUtf8Entry(name)); assert(name.equals(e.stringValue())); classEntries.put(e.stringValue(), e); } @@ -72,7 +65,8 @@ class ConstantPool implements Constants { } /** Factory for literal constants (String, Integer, etc.). */ public static synchronized LiteralEntry getLiteralEntry(Comparable value) { - LiteralEntry e = (LiteralEntry) literalEntries.get(value); + Map literalEntries = Utils.getLiteralEntries(); + LiteralEntry e = literalEntries.get(value); if (e == null) { if (value instanceof String) e = new StringEntry(getUtf8Entry((String)value)); @@ -89,7 +83,8 @@ class ConstantPool implements Constants { /** Factory for signature (type) constants. */ public static synchronized SignatureEntry getSignatureEntry(String type) { - SignatureEntry e = (SignatureEntry) signatureEntries.get(type); + Map signatureEntries = Utils.getSignatureEntries(); + SignatureEntry e = signatureEntries.get(type); if (e == null) { e = new SignatureEntry(type); assert(e.stringValue().equals(type)); @@ -104,8 +99,9 @@ class ConstantPool implements Constants { /** Factory for descriptor (name-and-type) constants. */ public static synchronized DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, SignatureEntry typeRef) { + Map descriptorEntries = Utils.getDescriptorEntries(); String key = DescriptorEntry.stringValueOf(nameRef, typeRef); - DescriptorEntry e = (DescriptorEntry) descriptorEntries.get(key); + DescriptorEntry e = descriptorEntries.get(key); if (e == null) { e = new DescriptorEntry(nameRef, typeRef); assert(e.stringValue().equals(key)) @@ -121,8 +117,9 @@ class ConstantPool implements Constants { /** Factory for member reference constants. */ public static synchronized MemberEntry getMemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) { + Map memberEntries = Utils.getMemberEntries(); String key = MemberEntry.stringValueOf(tag, classRef, descRef); - MemberEntry e = (MemberEntry) memberEntries.get(key); + MemberEntry e = memberEntries.get(key); if (e == null) { e = new MemberEntry(tag, classRef, descRef); assert(e.stringValue().equals(key)) @@ -489,8 +486,9 @@ class ConstantPool implements Constants { String[] parts = structureSignature(value); formRef = getUtf8Entry(parts[0]); classRefs = new ClassEntry[parts.length-1]; - for (int i = 1; i < parts.length; i++) - classRefs[i-1] = getClassEntry(parts[i]); + for (int i = 1; i < parts.length; i++) { + classRefs[i - 1] = getClassEntry(parts[i]); + } hashCode(); // force computation of valueHash } protected int computeValueHash() { @@ -527,8 +525,9 @@ class ConstantPool implements Constants { String stringValueOf(Utf8Entry formRef, ClassEntry[] classRefs) { String[] parts = new String[1+classRefs.length]; parts[0] = formRef.stringValue(); - for (int i = 1; i < parts.length; i++) - parts[i] = classRefs[i-1].stringValue(); + for (int i = 1; i < parts.length; i++) { + parts[i] = classRefs[i - 1].stringValue(); + } return flattenSignature(parts).intern(); } @@ -543,19 +542,23 @@ class ConstantPool implements Constants { int size = 0; for (int i = min; i < max; i++) { switch (form.charAt(i)) { - case 'D': - case 'J': - if (countDoublesTwice) size++; - break; - case '[': - // Skip rest of array info. - while (form.charAt(i) == '[') ++i; - break; - case ';': - continue; - default: - assert(0 <= JAVA_SIGNATURE_CHARS.indexOf(form.charAt(i))); - break; + case 'D': + case 'J': + if (countDoublesTwice) { + size++; + } + break; + case '[': + // Skip rest of array info. + while (form.charAt(i) == '[') { + ++i; + } + break; + case ';': + continue; + default: + assert (0 <= JAVA_SIGNATURE_CHARS.indexOf(form.charAt(i))); + break; } size++; } @@ -586,8 +589,9 @@ class ConstantPool implements Constants { s = "/" + formRef.stringValue(); } int i; - while ((i = s.indexOf(';')) >= 0) - s = s.substring(0,i) + s.substring(i+1); + while ((i = s.indexOf(';')) >= 0) { + s = s.substring(0, i) + s.substring(i + 1); + } return s; } } @@ -732,11 +736,11 @@ class ConstantPool implements Constants { clearIndex(); this.cpMap = cpMap; } - protected Index(String debugName, Collection cpMapList) { + protected Index(String debugName, Collection cpMapList) { this(debugName); setMap(cpMapList); } - protected void setMap(Collection cpMapList) { + protected void setMap(Collection cpMapList) { cpMap = new Entry[cpMapList.size()]; cpMapList.toArray(cpMap); setMap(cpMap); @@ -756,11 +760,13 @@ class ConstantPool implements Constants { // // As a special hack, if flattenSigs, signatures are // treated as equivalent entries of cpMap. This is wrong - // fron a Collection point of view, because contains() + // from a Collection point of view, because contains() // reports true for signatures, but the iterator() // never produces them! private int findIndexOf(Entry e) { - if (indexKey == null) initializeIndex(); + if (indexKey == null) { + initializeIndex(); + } int probe = findIndexLocation(e); if (indexKey[probe] != e) { if (flattenSigs && e.tag == CONSTANT_Signature) { @@ -832,7 +838,9 @@ class ConstantPool implements Constants { System.out.println("initialize Index "+debugName+" ["+size()+"]"); int hsize0 = (int)((cpMap.length + 10) * 1.5); int hsize = 1; - while (hsize < hsize0) hsize <<= 1; + while (hsize < hsize0) { + hsize <<= 1; + } indexKey = new Entry[hsize]; indexValue = new int[hsize]; for (int i = 0; i < cpMap.length; i++) { @@ -855,7 +863,7 @@ class ConstantPool implements Constants { return toArray(new Entry[size()]); } public Object clone() { - return new Index(debugName, (Entry[]) cpMap.clone()); + return new Index(debugName, cpMap.clone()); } public String toString() { return "Index "+debugName+" ["+size()+"]"; @@ -901,22 +909,24 @@ class ConstantPool implements Constants { public static Index[] partition(Index ix, int[] keys) { // %%% Should move this into class Index. - ArrayList parts = new ArrayList(); + ArrayList> parts = new ArrayList<>(); Entry[] cpMap = ix.cpMap; assert(keys.length == cpMap.length); for (int i = 0; i < keys.length; i++) { int key = keys[i]; if (key < 0) continue; - while (key >= parts.size()) parts.add(null); - ArrayList part = (ArrayList) parts.get(key); + while (key >= parts.size()) { + parts.add(null); + } + List part = parts.get(key); if (part == null) { - parts.set(key, part = new ArrayList()); + parts.set(key, part = new ArrayList<>()); } part.add(cpMap[i]); } Index[] indexes = new Index[parts.size()]; for (int key = 0; key < indexes.length; key++) { - ArrayList part = (ArrayList) parts.get(key); + List part = parts.get(key); if (part == null) continue; indexes[key] = new Index(ix.debugName+"/part#"+key, part); assert(indexes[key].indexOf(part.get(0)) == 0); @@ -1048,9 +1058,10 @@ class ConstantPool implements Constants { whichClasses[i] = whichClass; } perClassIndexes = partition(allMembers, whichClasses); - for (int i = 0; i < perClassIndexes.length; i++) - assert(perClassIndexes[i]==null - || perClassIndexes[i].assertIsSorted()); + for (int i = 0; i < perClassIndexes.length; i++) { + assert (perClassIndexes[i] == null || + perClassIndexes[i].assertIsSorted()); + } indexByTagAndClass[tag] = perClassIndexes; } int whichClass = allClasses.indexOf(classRef); @@ -1113,7 +1124,7 @@ class ConstantPool implements Constants { * Also, discard null from cpRefs. */ public static - void completeReferencesIn(Set cpRefs, boolean flattenSigs) { + void completeReferencesIn(Set cpRefs, boolean flattenSigs) { cpRefs.remove(null); for (ListIterator work = new ArrayList(cpRefs).listIterator(cpRefs.size()); diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/Driver.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/Driver.java index 50d145db99f..2630febb7c3 100644 --- a/jdk/src/share/classes/com/sun/java/util/jar/pack/Driver.java +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/Driver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -25,7 +25,6 @@ package com.sun.java.util.jar.pack; -import java.lang.Error; import java.io.*; import java.text.MessageFormat; import java.util.*; @@ -35,10 +34,11 @@ import java.util.zip.*; /** Command line interface for Pack200. */ class Driver { - private static final ResourceBundle RESOURCE= ResourceBundle.getBundle("com.sun.java.util.jar.pack.DriverResource"); + private static final ResourceBundle RESOURCE = + ResourceBundle.getBundle("com.sun.java.util.jar.pack.DriverResource"); public static void main(String[] ava) throws IOException { - ArrayList av = new ArrayList(Arrays.asList(ava)); + ArrayList av = new ArrayList<>(Arrays.asList(ava)); boolean doPack = true; boolean doUnpack = false; @@ -61,7 +61,7 @@ class Driver { } // Collect engine properties here: - HashMap engProps = new HashMap(); + HashMap engProps = new HashMap<>(); engProps.put(verboseProp, System.getProperty(verboseProp)); String optionMap; @@ -75,7 +75,7 @@ class Driver { } // Collect argument properties here: - HashMap avProps = new HashMap(); + HashMap avProps = new HashMap<>(); try { for (;;) { String state = parseCommandOptions(av, optionMap, avProps); @@ -133,8 +133,9 @@ class Driver { if (engProps.get(verboseProp) != null) fileProps.list(System.out); propIn.close(); - for (Map.Entry me : fileProps.entrySet()) - engProps.put((String)me.getKey(), (String)me.getValue()); + for (Map.Entry me : fileProps.entrySet()) { + engProps.put((String) me.getKey(), (String) me.getValue()); + } } else if (state == "--version") { System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.VERSION), Driver.class.getName(), "1.31, 07/05/05")); return; @@ -493,7 +494,7 @@ class Driver { String resultString = null; // Convert options string into optLines dictionary. - TreeMap optmap = new TreeMap(); + TreeMap optmap = new TreeMap<>(); loadOptmap: for (String optline : options.split("\n")) { String[] words = optline.split("\\p{Space}+"); @@ -687,7 +688,9 @@ class Driver { // Report number of arguments consumed. args.subList(0, argp.nextIndex()).clear(); // Report any unconsumed partial argument. - while (pbp.hasPrevious()) args.add(0, pbp.previous()); + while (pbp.hasPrevious()) { + args.add(0, pbp.previous()); + } //System.out.println(args+" // "+properties+" -> "+resultString); return resultString; } diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/NativeUnpack.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/NativeUnpack.java index f8c55dc6656..8b54ce3a7e1 100644 --- a/jdk/src/share/classes/com/sun/java/util/jar/pack/NativeUnpack.java +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/NativeUnpack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -28,13 +28,8 @@ package com.sun.java.util.jar.pack; import java.nio.*; import java.io.*; -import java.nio.channels.*; -import java.util.Date; import java.util.jar.*; import java.util.zip.*; -import java.util.*; -//import com.sun.java.util.jar.pack.Pack200; - class NativeUnpack { // Pointer to the native unpacker obj @@ -91,13 +86,13 @@ class NativeUnpack { NativeUnpack(UnpackerImpl p200) { super(); _p200 = p200; - _props = p200._props; + _props = p200.props; p200._nunp = this; } // for JNI callbacks static private Object currentInstance() { - UnpackerImpl p200 = (UnpackerImpl) Utils.currentInstance.get(); + UnpackerImpl p200 = (UnpackerImpl) Utils.getTLGlobals(); return (p200 == null)? null: p200._nunp; } diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/Package.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/Package.java index 1ea04690ed8..29d217687c4 100644 --- a/jdk/src/share/classes/com/sun/java/util/jar/pack/Package.java +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/Package.java @@ -25,9 +25,9 @@ package com.sun.java.util.jar.pack; +import com.sun.java.util.jar.pack.Attribute.Layout; import java.lang.reflect.Modifier; import java.util.*; -import java.util.zip.*; import java.util.jar.*; import java.io.*; import com.sun.java.util.jar.pack.ConstantPool.*; @@ -77,10 +77,11 @@ class Package implements Constants { cp = new ConstantPool.IndexGroup(); classes.clear(); files.clear(); + BandStructure.nextSeqForDebug = 0; } int getPackageVersion() { - return (package_majver << 16) + (int)package_minver; + return (package_majver << 16) + package_minver; } // Special empty versions of Code and InnerClasses, used for markers. @@ -89,7 +90,7 @@ class Package implements Constants { public static final Attribute.Layout attrSourceFileSpecial; public static final Map attrDefs; static { - HashMap ad = new HashMap(2); + HashMap ad = new HashMap<>(3); attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD, "Code", "").layout(); attrInnerClassesEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, @@ -159,9 +160,9 @@ class Package implements Constants { } } - ArrayList classes = new ArrayList(); + ArrayList classes = new ArrayList<>(); - public List getClasses() { + public List getClasses() { return classes; } @@ -186,11 +187,11 @@ class Package implements Constants { ClassEntry[] interfaces; // Class parts - ArrayList fields; - ArrayList methods; + ArrayList fields; + ArrayList methods; //ArrayList attributes; // in Attribute.Holder.this.attributes // Note that InnerClasses may be collected at the package level. - ArrayList innerClasses; + ArrayList innerClasses; Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { this.magic = JAVA_MAGIC; @@ -270,7 +271,7 @@ class Package implements Constants { if (a != olda) { if (verbose > 2) Utils.log.fine("recoding obvious SourceFile="+obvious); - List newAttrs = new ArrayList(getAttributes()); + List newAttrs = new ArrayList<>(getAttributes()); int where = newAttrs.indexOf(olda); newAttrs.set(where, a); setAttributes(newAttrs); @@ -295,12 +296,12 @@ class Package implements Constants { boolean hasInnerClasses() { return innerClasses != null; } - List getInnerClasses() { + List getInnerClasses() { return innerClasses; } - public void setInnerClasses(Collection ics) { - innerClasses = (ics == null) ? null : new ArrayList(ics); + public void setInnerClasses(Collection ics) { + innerClasses = (ics == null) ? null : new ArrayList(ics); // Edit the attribute list, if necessary. Attribute a = getAttribute(attrInnerClassesEmpty); if (innerClasses != null && a == null) @@ -318,19 +319,18 @@ class Package implements Constants { * The order of the resulting list is consistent * with that of Package.this.allInnerClasses. */ - public List computeGloballyImpliedICs() { - HashSet cpRefs = new HashSet(); + public List computeGloballyImpliedICs() { + HashSet cpRefs = new HashSet<>(); { // This block temporarily displaces this.innerClasses. - ArrayList innerClassesSaved = innerClasses; + ArrayList innerClassesSaved = innerClasses; innerClasses = null; // ignore for the moment visitRefs(VRM_CLASSIC, cpRefs); innerClasses = innerClassesSaved; } ConstantPool.completeReferencesIn(cpRefs, true); - HashSet icRefs = new HashSet(); - for (Iterator i = cpRefs.iterator(); i.hasNext(); ) { - Entry e = (Entry) i.next(); + HashSet icRefs = new HashSet<>(); + for (Entry e : cpRefs) { // Restrict cpRefs to InnerClasses entries only. if (!(e instanceof ClassEntry)) continue; // For every IC reference, add its outers also. @@ -345,9 +345,8 @@ class Package implements Constants { // This loop is structured this way so as to accumulate // entries into impliedICs in an order which reflects // the order of allInnerClasses. - ArrayList impliedICs = new ArrayList(); - for (Iterator i = allInnerClasses.iterator(); i.hasNext(); ) { - InnerClass ic = (InnerClass) i.next(); + ArrayList impliedICs = new ArrayList<>(); + for (InnerClass ic : allInnerClasses) { // This one is locally relevant if it describes // a member of the current class, or if the current // class uses it somehow. In the particular case @@ -366,10 +365,11 @@ class Package implements Constants { // Helper for both minimizing and expanding. // Computes a symmetric difference. - private List computeICdiff() { - List impliedICs = computeGloballyImpliedICs(); - List actualICs = getInnerClasses(); - if (actualICs == null) actualICs = Collections.EMPTY_LIST; + private List computeICdiff() { + List impliedICs = computeGloballyImpliedICs(); + List actualICs = getInnerClasses(); + if (actualICs == null) + actualICs = Collections.EMPTY_LIST; // Symmetric difference is calculated from I, A like this: // diff = (I+A) - (I*A) @@ -388,8 +388,8 @@ class Package implements Constants { // Diff is A since I is empty. } // (I*A) is non-trivial - HashSet center = new HashSet(actualICs); - center.retainAll(new HashSet(impliedICs)); + HashSet center = new HashSet<>(actualICs); + center.retainAll(new HashSet<>(impliedICs)); impliedICs.addAll(actualICs); impliedICs.removeAll(center); // Diff is now I^A = (I+A)-(I*A). @@ -407,9 +407,9 @@ class Package implements Constants { * to use the globally implied ICs changed. */ void minimizeLocalICs() { - List diff = computeICdiff(); - List actualICs = innerClasses; - List localICs; // will be the diff, modulo edge cases + List diff = computeICdiff(); + List actualICs = innerClasses; + List localICs; // will be the diff, modulo edge cases if (diff.isEmpty()) { // No diff, so transmit no attribute. localICs = null; @@ -439,12 +439,12 @@ class Package implements Constants { * Otherwise, return positive if any IC tuples were added. */ int expandLocalICs() { - List localICs = innerClasses; - List actualICs; + List localICs = innerClasses; + List actualICs; int changed; if (localICs == null) { // Diff was empty. (Common case.) - List impliedICs = computeGloballyImpliedICs(); + List impliedICs = computeGloballyImpliedICs(); if (impliedICs.isEmpty()) { actualICs = null; changed = 0; @@ -490,7 +490,7 @@ class Package implements Constants { protected Entry[] getCPMap() { return cpMap; } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { if (verbose > 2) Utils.log.fine("visitRefs "+this); // Careful: The descriptor is used by the package, // but the classfile breaks it into component refs. @@ -518,7 +518,7 @@ class Package implements Constants { super(flags, descriptor); assert(!descriptor.isMethod()); if (fields == null) - fields = new ArrayList(); + fields = new ArrayList<>(); boolean added = fields.add(this); assert(added); order = fields.size(); @@ -543,7 +543,7 @@ class Package implements Constants { super(flags, descriptor); assert(descriptor.isMethod()); if (methods == null) - methods = new ArrayList(); + methods = new ArrayList<>(); boolean added = methods.add(this); assert(added); } @@ -573,7 +573,7 @@ class Package implements Constants { code.strip(attrName); super.strip(attrName); } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { super.visitRefs(mode, refs); if (code != null) { if (mode == VRM_CLASSIC) { @@ -614,7 +614,7 @@ class Package implements Constants { super.strip(attrName); } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { if (verbose > 2) Utils.log.fine("visitRefs "+this); refs.add(thisClass); refs.add(superClass); @@ -641,7 +641,7 @@ class Package implements Constants { super.visitRefs(mode, refs); } - protected void visitInnerClassRefs(int mode, Collection refs) { + protected void visitInnerClassRefs(int mode, Collection refs) { Package.visitInnerClassRefs(innerClasses, mode, refs); } @@ -713,16 +713,15 @@ class Package implements Constants { } // What non-class files are in this unit? - ArrayList files = new ArrayList(); + ArrayList files = new ArrayList<>(); - public List getFiles() { + public List getFiles() { return files; } - public List getClassStubs() { - ArrayList classStubs = new ArrayList(classes.size()); - for (Iterator i = classes.iterator(); i.hasNext(); ) { - Class cls = (Class) i.next(); + public List getClassStubs() { + ArrayList classStubs = new ArrayList<>(classes.size()); + for (Class cls : classes) { assert(cls.file.isClassStub()); classStubs.add(cls.file); } @@ -840,7 +839,7 @@ class Package implements Constants { public InputStream getInputStream() { InputStream in = new ByteArrayInputStream(append.toByteArray()); if (prepend.size() == 0) return in; - ArrayList isa = new ArrayList(prepend.size()+1); + ArrayList isa = new ArrayList<>(prepend.size()+1); for (Iterator i = prepend.iterator(); i.hasNext(); ) { byte[] bytes = (byte[]) i.next(); isa.add(new ByteArrayInputStream(bytes)); @@ -849,7 +848,7 @@ class Package implements Constants { return new SequenceInputStream(Collections.enumeration(isa)); } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { assert(name != null); refs.add(name); } @@ -877,8 +876,8 @@ class Package implements Constants { } // Is there a globally declared table of inner classes? - ArrayList allInnerClasses = new ArrayList(); - HashMap allInnerClassesByThis; + ArrayList allInnerClasses = new ArrayList<>(); + HashMap allInnerClassesByThis; public List getAllInnerClasses() { @@ -886,15 +885,14 @@ class Package implements Constants { } public - void setAllInnerClasses(Collection ics) { + void setAllInnerClasses(Collection ics) { assert(ics != allInnerClasses); allInnerClasses.clear(); allInnerClasses.addAll(ics); // Make an index: - allInnerClassesByThis = new HashMap(allInnerClasses.size()); - for (Iterator i = allInnerClasses.iterator(); i.hasNext(); ) { - InnerClass ic = (InnerClass) i.next(); + allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); + for (InnerClass ic : allInnerClasses) { Object pic = allInnerClassesByThis.put(ic.thisClass, ic); assert(pic == null); // caller must ensure key uniqueness! } @@ -904,7 +902,7 @@ class Package implements Constants { public InnerClass getGlobalInnerClass(Entry thisClass) { assert(thisClass instanceof ClassEntry); - return (InnerClass) allInnerClassesByThis.get(thisClass); + return allInnerClassesByThis.get(thisClass); } static @@ -963,7 +961,7 @@ class Package implements Constants { return this.thisClass.compareTo(that.thisClass); } - protected void visitRefs(int mode, Collection refs) { + protected void visitRefs(int mode, Collection refs) { refs.add(thisClass); if (mode == VRM_CLASSIC || !predictable) { // If the name can be demangled, the package omits @@ -980,7 +978,7 @@ class Package implements Constants { // Helper for building InnerClasses attributes. static private - void visitInnerClassRefs(Collection innerClasses, int mode, Collection refs) { + void visitInnerClassRefs(Collection innerClasses, int mode, Collection refs) { if (innerClasses == null) { return; // no attribute; nothing to do } @@ -1165,9 +1163,8 @@ class Package implements Constants { } } - protected void visitRefs(int mode, Collection refs) { - for (Iterator i = classes.iterator(); i.hasNext(); ) { - Class c = (Class)i.next(); + protected void visitRefs(int mode, Collection refs) { + for ( Class c : classes) { c.visitRefs(mode, refs); } if (mode != VRM_CLASSIC) { @@ -1259,7 +1256,7 @@ class Package implements Constants { } // Use this before writing the package file. - void buildGlobalConstantPool(Set requiredEntries) { + void buildGlobalConstantPool(Set requiredEntries) { if (verbose > 1) Utils.log.fine("Checking for unused CP entries"); requiredEntries.add(getRefString("")); // uconditionally present @@ -1291,9 +1288,8 @@ class Package implements Constants { // Use this before writing the class files. void ensureAllClassFiles() { - HashSet fileSet = new HashSet(files); - for (Iterator i = classes.iterator(); i.hasNext(); ) { - Class cls = (Class) i.next(); + HashSet fileSet = new HashSet<>(files); + for (Class cls : classes) { // Add to the end of ths list: if (!fileSet.contains(cls.file)) files.add(cls.file); 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 9c285794303..449bbbeb5b1 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -25,12 +25,11 @@ package com.sun.java.util.jar.pack; +import com.sun.java.util.jar.pack.Attribute.Layout; import java.util.*; import java.util.jar.*; -import java.util.zip.*; import java.io.*; import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; /* @@ -41,31 +40,22 @@ import java.beans.PropertyChangeEvent; */ -public class PackerImpl implements Pack200.Packer { +public class PackerImpl extends TLGlobals implements Pack200.Packer { /** * Constructs a Packer object and sets the initial state of * the packer engines. */ - public PackerImpl() { - _props = new PropMap(); - //_props.getProperty() consults defaultProps invisibly. - //_props.putAll(defaultProps); - } - - - // Private stuff. - final PropMap _props; + public PackerImpl() {} /** * Get the set of options for the pack and unpack engines. * @return A sorted association of option key strings to option values. */ - public SortedMap properties() { - return _props; + public SortedMap properties() { + return props; } - //Driver routines /** @@ -78,21 +68,22 @@ public class PackerImpl implements Pack200.Packer { */ public void pack(JarFile in, OutputStream out) throws IOException { assert(Utils.currentInstance.get() == null); - TimeZone tz = (_props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null : - TimeZone.getDefault(); + TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) + ? null + : TimeZone.getDefault(); try { Utils.currentInstance.set(this); if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - if ("0".equals(_props.getProperty(Pack200.Packer.EFFORT))) { + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { Utils.copyJarFile(in, out); } else { (new DoPack()).run(in, out); - in.close(); } } finally { Utils.currentInstance.set(null); if (tz != null) TimeZone.setDefault(tz); + in.close(); } } @@ -112,21 +103,20 @@ public class PackerImpl implements Pack200.Packer { */ public void pack(JarInputStream in, OutputStream out) throws IOException { assert(Utils.currentInstance.get() == null); - TimeZone tz = (_props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null : + TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null : TimeZone.getDefault(); try { Utils.currentInstance.set(this); if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - if ("0".equals(_props.getProperty(Pack200.Packer.EFFORT))) { + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { Utils.copyJarFile(in, out); } else { (new DoPack()).run(in, out); - in.close(); } } finally { Utils.currentInstance.set(null); if (tz != null) TimeZone.setDefault(tz); - + in.close(); } } /** @@ -134,7 +124,7 @@ public class PackerImpl implements Pack200.Packer { * @param listener An object to be invoked when a property is changed. */ public void addPropertyChangeListener(PropertyChangeListener listener) { - _props.addListener(listener); + props.addListener(listener); } /** @@ -142,7 +132,7 @@ public class PackerImpl implements Pack200.Packer { * @param listener The PropertyChange listener to be removed. */ public void removePropertyChangeListener(PropertyChangeListener listener) { - _props.removeListener(listener); + props.removeListener(listener); } @@ -151,11 +141,11 @@ public class PackerImpl implements Pack200.Packer { // The packer worker. private class DoPack { - final int verbose = _props.getInteger(Utils.DEBUG_VERBOSE); + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); { - _props.setInteger(Pack200.Packer.PROGRESS, 0); - if (verbose > 0) Utils.log.info(_props.toString()); + props.setInteger(Pack200.Packer.PROGRESS, 0); + if (verbose > 0) Utils.log.info(props.toString()); } // Here's where the bits are collected before getting packed: @@ -163,7 +153,7 @@ public class PackerImpl implements Pack200.Packer { final String unknownAttrCommand; { - String uaMode = _props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); + String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); if (!(Pack200.Packer.STRIP.equals(uaMode) || Pack200.Packer.PASS.equals(uaMode) || Pack200.Packer.ERROR.equals(uaMode))) { @@ -191,13 +181,12 @@ public class PackerImpl implements Pack200.Packer { }; for (int i = 0; i < ctypes.length; i++) { String pfx = keys[i]; - Map map = _props.prefixMap(pfx); - for (Iterator j = map.keySet().iterator(); j.hasNext(); ) { - String key = (String) j.next(); + Map map = props.prefixMap(pfx); + for (String key : map.keySet()) { assert(key.startsWith(pfx)); String name = key.substring(pfx.length()); - String layout = _props.getProperty(key); - Object lkey = Attribute.keyForLookup(ctypes[i], name); + String layout = props.getProperty(key); + Layout lkey = Attribute.keyForLookup(ctypes[i], name); if (Pack200.Packer.STRIP.equals(layout) || Pack200.Packer.PASS.equals(layout) || Pack200.Packer.ERROR.equals(layout)) { @@ -222,25 +211,25 @@ public class PackerImpl implements Pack200.Packer { } final boolean keepFileOrder - = _props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); + = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); final boolean keepClassOrder - = _props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); + = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); final boolean keepModtime - = Pack200.Packer.KEEP.equals(_props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); final boolean latestModtime - = Pack200.Packer.LATEST.equals(_props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); final boolean keepDeflateHint - = Pack200.Packer.KEEP.equals(_props.getProperty(Pack200.Packer.DEFLATE_HINT)); + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT)); { if (!keepModtime && !latestModtime) { - int modtime = _props.getTime(Pack200.Packer.MODIFICATION_TIME); + int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME); if (modtime != Constants.NO_MODTIME) { pkg.default_modtime = modtime; } } if (!keepDeflateHint) { - boolean deflate_hint = _props.getBoolean(Pack200.Packer.DEFLATE_HINT); + boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT); if (deflate_hint) { pkg.default_options |= Constants.AO_DEFLATE_HINT; } @@ -254,10 +243,10 @@ public class PackerImpl implements Pack200.Packer { final long segmentLimit; { long limit; - if (_props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) + if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) limit = -1; else - limit = _props.getLong(Pack200.Packer.SEGMENT_LIMIT); + limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT); limit = Math.min(Integer.MAX_VALUE, limit); limit = Math.max(-1, limit); if (limit == -1) @@ -265,10 +254,10 @@ public class PackerImpl implements Pack200.Packer { segmentLimit = limit; } - final List passFiles; // parsed pack.pass.file options + final List passFiles; // parsed pack.pass.file options { // Which class files will be passed through? - passFiles = _props.getProperties(Pack200.Packer.PASS_FILE_PFX); + passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX); for (ListIterator i = passFiles.listIterator(); i.hasNext(); ) { String file = (String) i.next(); if (file == null) { i.remove(); continue; } @@ -283,28 +272,28 @@ public class PackerImpl implements Pack200.Packer { { // Fill in permitted range of major/minor version numbers. int ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"min.class.majver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"min.class.majver")) != 0) pkg.min_class_majver = (short) ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"min.class.minver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"min.class.minver")) != 0) pkg.min_class_minver = (short) ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"max.class.majver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"max.class.majver")) != 0) pkg.max_class_majver = (short) ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"max.class.minver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"max.class.minver")) != 0) pkg.max_class_minver = (short) ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"package.minver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"package.minver")) != 0) pkg.package_minver = (short) ver; - if ((ver = _props.getInteger(Utils.COM_PREFIX+"package.majver")) != 0) + if ((ver = props.getInteger(Utils.COM_PREFIX+"package.majver")) != 0) pkg.package_majver = (short) ver; } { // Hook for testing: Forces use of special archive modes. - int opt = _props.getInteger(Utils.COM_PREFIX+"archive.options"); + int opt = props.getInteger(Utils.COM_PREFIX+"archive.options"); if (opt != 0) pkg.default_options |= opt; } - // (Done collecting options from _props.) + // (Done collecting options from props.) boolean isClassFile(String name) { if (!name.endsWith(".class")) return false; @@ -423,16 +412,18 @@ public class PackerImpl implements Pack200.Packer { Package.File file = null; // (5078608) : discount the resource files in META-INF // from segment computation. - long inflen = (isMetaInfFile(name)) ? 0L : - inFile.getInputLength(); + long inflen = (isMetaInfFile(name)) + ? 0L + : inFile.getInputLength(); if ((segmentSize += inflen) > segmentLimit) { segmentSize -= inflen; int nextCount = -1; // don't know; it's a stream flushPartial(out, nextCount); } - if (verbose > 1) + if (verbose > 1) { Utils.log.fine("Reading " + name); + } assert(je.isDirectory() == name.endsWith("/")); @@ -450,18 +441,18 @@ public class PackerImpl implements Pack200.Packer { } void run(JarFile in, OutputStream out) throws IOException { - List inFiles = scanJar(in); + List inFiles = scanJar(in); if (verbose > 0) Utils.log.info("Reading " + inFiles.size() + " files..."); int numDone = 0; - for (Iterator i = inFiles.iterator(); i.hasNext(); ) { - InFile inFile = (InFile) i.next(); + for (InFile inFile : inFiles) { String name = inFile.name; // (5078608) : discount the resource files completely from segmenting - long inflen = (isMetaInfFile(name)) ? 0L : - inFile.getInputLength() ; + long inflen = (isMetaInfFile(name)) + ? 0L + : inFile.getInputLength() ; if ((segmentSize += inflen) > segmentLimit) { segmentSize -= inflen; // Estimate number of remaining segments: @@ -530,11 +521,11 @@ public class PackerImpl implements Pack200.Packer { } void flushPartial(OutputStream out, int nextCount) throws IOException { - if (pkg.files.size() == 0 && pkg.classes.size() == 0) { + if (pkg.files.isEmpty() && pkg.classes.isEmpty()) { return; // do not flush an empty segment } flushPackage(out, Math.max(1, nextCount)); - _props.setInteger(Pack200.Packer.PROGRESS, 25); + props.setInteger(Pack200.Packer.PROGRESS, 25); // In case there will be another segment: makeNextPackage(); segmentCount += 1; @@ -543,10 +534,10 @@ public class PackerImpl implements Pack200.Packer { } void flushAll(OutputStream out) throws IOException { - _props.setInteger(Pack200.Packer.PROGRESS, 50); + props.setInteger(Pack200.Packer.PROGRESS, 50); flushPackage(out, 0); out.flush(); - _props.setInteger(Pack200.Packer.PROGRESS, 100); + props.setInteger(Pack200.Packer.PROGRESS, 100); segmentCount += 1; segmentTotalSize += segmentSize; segmentSize = 0; @@ -582,11 +573,11 @@ public class PackerImpl implements Pack200.Packer { pkg.trimStubs(); // Do some stripping, maybe. - if (_props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); - if (_props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); - if (_props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); - if (_props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); - if (_props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); // Must choose an archive version; PackageWriter does not. if (pkg.package_majver <= 0) pkg.choosePackageVersion(); @@ -606,11 +597,10 @@ public class PackerImpl implements Pack200.Packer { } } - List scanJar(JarFile jf) throws IOException { + List scanJar(JarFile jf) throws IOException { // Collect jar entries, preserving order. - List inFiles = new ArrayList(); - for (Enumeration e = jf.entries(); e.hasMoreElements(); ) { - JarEntry je = (JarEntry) e.nextElement(); + List inFiles = new ArrayList<>(); + for (JarEntry je : Collections.list(jf.entries())) { InFile inFile = new InFile(jf, je); assert(je.isDirectory() == inFile.name.endsWith("/")); inFiles.add(inFile); diff --git a/jdk/src/share/classes/com/sun/java/util/jar/pack/TLGlobals.java b/jdk/src/share/classes/com/sun/java/util/jar/pack/TLGlobals.java new file mode 100644 index 00000000000..0e40bde8b63 --- /dev/null +++ b/jdk/src/share/classes/com/sun/java/util/jar/pack/TLGlobals.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.sun.java.util.jar.pack; + +import com.sun.java.util.jar.pack.ConstantPool.ClassEntry; +import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; +import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; +import com.sun.java.util.jar.pack.ConstantPool.MemberEntry; +import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry; +import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; + +/* + * @author ksrini + */ + +/* + * This class provides a container to hold the global variables, for packer + * and unpacker instances. This is typically stashed away in a ThreadLocal, + * and the storage is destroyed upon completion. Therefore any local + * references to these members must be eliminated appropriately to prevent a + * memory leak. + */ +class TLGlobals { + // Global environment + final PropMap props; + + // Needed by ConstantPool.java + private final Map utf8Entries; + private final Map classEntries; + private final Map literalEntries; + private final Map signatureEntries; + private final Map descriptorEntries; + private final Map memberEntries; + + TLGlobals() { + utf8Entries = new HashMap<>(); + classEntries = new HashMap<>(); + literalEntries = new HashMap<>(); + signatureEntries = new HashMap<>(); + descriptorEntries = new HashMap<>(); + memberEntries = new HashMap<>(); + props = new PropMap(); + } + + SortedMap getPropMap() { + return props; + } + + Map getUtf8Entries() { + return utf8Entries; + } + + Map getClassEntries() { + return classEntries; + } + + Map getLiteralEntries() { + return literalEntries; + } + + Map getDescriptorEntries() { + return descriptorEntries; + } + + Map getSignatureEntries() { + return signatureEntries; + } + + Map getMemberEntries() { + return memberEntries; + } +} 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 d131a1255f4..11c8abf07f1 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -30,7 +30,6 @@ import java.util.jar.*; import java.util.zip.*; import java.io.*; import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; /* * Implementation of the Pack provider. @@ -40,7 +39,7 @@ import java.beans.PropertyChangeEvent; */ -public class UnpackerImpl implements Pack200.Unpacker { +public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker { /** @@ -48,7 +47,7 @@ public class UnpackerImpl implements Pack200.Unpacker { * @param listener An object to be invoked when a property is changed. */ public void addPropertyChangeListener(PropertyChangeListener listener) { - _props.addListener(listener); + props.addListener(listener); } @@ -57,25 +56,19 @@ public class UnpackerImpl implements Pack200.Unpacker { * @param listener The PropertyChange listener to be removed. */ public void removePropertyChangeListener(PropertyChangeListener listener) { - _props.removeListener(listener); + props.removeListener(listener); } - public UnpackerImpl() { - _props = new PropMap(); - //_props.getProperty() consults defaultProps invisibly. - //_props.putAll(defaultProps); - } + public UnpackerImpl() {} - // Private stuff. - final PropMap _props; /** * Get the set of options for the pack and unpack engines. * @return A sorted association of option key strings to option values. */ - public SortedMap properties() { - return _props; + public SortedMap properties() { + return props; } // Back-pointer to NativeUnpacker, when active. @@ -101,19 +94,20 @@ public class UnpackerImpl implements Pack200.Unpacker { */ public void unpack(InputStream in0, JarOutputStream out) throws IOException { assert(Utils.currentInstance.get() == null); - TimeZone tz = (_props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) ? null : - TimeZone.getDefault(); + TimeZone tz = (props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)) + ? null + : TimeZone.getDefault(); try { Utils.currentInstance.set(this); if (tz != null) TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - final int verbose = _props.getInteger(Utils.DEBUG_VERBOSE); + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); BufferedInputStream in = new BufferedInputStream(in0); if (Utils.isJarMagic(Utils.readMagic(in))) { if (verbose > 0) Utils.log.info("Copying unpacked JAR file..."); Utils.copyJarFile(new JarInputStream(in), out); - } else if (_props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { + } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { (new DoUnpack()).run(in, out); in.close(); Utils.markJarFile(out); @@ -142,36 +136,38 @@ public class UnpackerImpl implements Pack200.Unpacker { // %%% Reconsider if native unpacker learns to memory-map the file. FileInputStream instr = new FileInputStream(in); unpack(instr, out); - if (_props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { + if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { in.delete(); } } private class DoUnpack { - final int verbose = _props.getInteger(Utils.DEBUG_VERBOSE); + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); { - _props.setInteger(Pack200.Unpacker.PROGRESS, 0); + props.setInteger(Pack200.Unpacker.PROGRESS, 0); } // Here's where the bits are read from disk: final Package pkg = new Package(); final boolean keepModtime - = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); + = Pack200.Packer.KEEP.equals( + props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); final boolean keepDeflateHint - = Pack200.Packer.KEEP.equals(_props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); + = Pack200.Packer.KEEP.equals( + props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); final int modtime; final boolean deflateHint; { if (!keepModtime) { - modtime = _props.getTime(Utils.UNPACK_MODIFICATION_TIME); + modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME); } else { modtime = pkg.default_modtime; } deflateHint = (keepDeflateHint) ? false : - _props.getBoolean(java.util.jar.Pack200.Unpacker.DEFLATE_HINT); + props.getBoolean(java.util.jar.Pack200.Unpacker.DEFLATE_HINT); } // Checksum apparatus. @@ -181,7 +177,7 @@ public class UnpackerImpl implements Pack200.Unpacker { public void run(BufferedInputStream in, JarOutputStream out) throws IOException { if (verbose > 0) { - _props.list(System.out); + props.list(System.out); } for (int seg = 1; ; seg++) { unpackSegment(in, out); @@ -194,25 +190,26 @@ public class UnpackerImpl implements Pack200.Unpacker { } private void unpackSegment(InputStream in, JarOutputStream out) throws IOException { - _props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"0"); + props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"0"); // Process the output directory or jar output. new PackageReader(pkg, in).read(); - if (_props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); - if (_props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); - _props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"50"); + if (props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); + props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"50"); pkg.ensureAllClassFiles(); // Now write out the files. - HashSet classesToWrite = new HashSet(pkg.getClasses()); + HashSet classesToWrite = new HashSet<>(pkg.getClasses()); for (Iterator i = pkg.getFiles().iterator(); i.hasNext(); ) { Package.File file = (Package.File) i.next(); String name = file.nameString; JarEntry je = new JarEntry(Utils.getJarEntryName(name)); boolean deflate; - deflate = (keepDeflateHint) ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || - ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) : - deflateHint; + deflate = (keepDeflateHint) + ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || + ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) + : deflateHint; boolean needCRC = !deflate; // STORE mode requires CRC @@ -250,7 +247,7 @@ public class UnpackerImpl implements Pack200.Unpacker { Utils.log.info("Writing "+Utils.zeString((ZipEntry)je)); } assert(classesToWrite.isEmpty()); - _props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"100"); + props.setProperty(java.util.jar.Pack200.Unpacker.PROGRESS,"100"); pkg.reset(); // reset for the next segment, if any } } 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 8edbed10c46..2dc47c41464 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2010, 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 @@ -25,6 +25,13 @@ package com.sun.java.util.jar.pack; +import com.sun.java.util.jar.pack.Attribute.Layout; +import com.sun.java.util.jar.pack.ConstantPool.ClassEntry; +import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; +import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; +import com.sun.java.util.jar.pack.ConstantPool.MemberEntry; +import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry; +import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; import java.util.*; import java.util.jar.*; import java.util.zip.*; @@ -113,17 +120,46 @@ class Utils { */ static final String PACK_ZIP_ARCHIVE_MARKER_COMMENT = "PACK200"; - // Keep a TLS point to the current Packer or Unpacker. - // This makes it simpler to supply environmental options + // Keep a TLS point to the global data and environment. + // This makes it simpler to supply environmental options // to the engine code, especially the native code. - static final ThreadLocal currentInstance = new ThreadLocal(); + static final ThreadLocal currentInstance = new ThreadLocal<>(); + + // convenience methods to access the TL globals + static TLGlobals getTLGlobals() { + return currentInstance.get(); + } + + static Map getUtf8Entries() { + return getTLGlobals().getUtf8Entries(); + } + + static Map getClassEntries() { + return getTLGlobals().getClassEntries(); + } + + static Map getLiteralEntries() { + return getTLGlobals().getLiteralEntries(); + } + + static Map getDescriptorEntries() { + return getTLGlobals().getDescriptorEntries(); + } + + static Map getSignatureEntries() { + return getTLGlobals().getSignatureEntries(); + } + + static Map getMemberEntries() { + return getTLGlobals().getMemberEntries(); + } static PropMap currentPropMap() { Object obj = currentInstance.get(); if (obj instanceof PackerImpl) - return ((PackerImpl)obj)._props; + return ((PackerImpl)obj).props; if (obj instanceof UnpackerImpl) - return ((UnpackerImpl)obj)._props; + return ((UnpackerImpl)obj).props; return null; } From 28fff3ad36a419733dee57a28c6232c7e2856ec6 Mon Sep 17 00:00:00 2001 From: Kumar Srinivasan Date: Fri, 20 Aug 2010 08:18:54 -0700 Subject: [PATCH 23/32] 6966737: (pack200) the pack200 regression tests need to be more robust Reviewed-by: jrose, ohair --- jdk/test/tools/pack200/CommandLineTests.java | 179 + jdk/test/tools/pack200/Pack200Simple.sh | 197 - jdk/test/tools/pack200/Pack200Test.java | 157 +- .../tools/pack200/PackageVersionTest.java | 47 +- jdk/test/tools/pack200/SegmentLimit.java | 105 +- jdk/test/tools/pack200/Utils.java | 499 ++ .../pack200/pack200-verifier/data/README | 45 + .../pack200/pack200-verifier/data/golden.jar | Bin 0 -> 418156 bytes .../pack200/pack200-verifier/make/build.xml | 59 + .../sun/tools/pack/verify/ClassCompare.java | 160 + .../src/sun/tools/pack/verify/Globals.java | 310 ++ .../sun/tools/pack/verify/JarFileCompare.java | 199 + .../src/sun/tools/pack/verify/Main.java | 171 + .../sun/tools/pack/verify/VerifyTreeSet.java | 46 + .../src/xmlkit/ClassReader.java | 1003 ++++ .../src/xmlkit/ClassSyntax.java | 518 ++ .../src/xmlkit/ClassWriter.java | 818 ++++ .../src/xmlkit/CommandLineParser.java | 284 ++ .../src/xmlkit/InstructionAssembler.java | 464 ++ .../src/xmlkit/InstructionSyntax.java | 483 ++ .../src/xmlkit/TokenList.java | 449 ++ .../pack200-verifier/src/xmlkit/XMLKit.java | 4330 +++++++++++++++++ 22 files changed, 10126 insertions(+), 397 deletions(-) create mode 100644 jdk/test/tools/pack200/CommandLineTests.java delete mode 100644 jdk/test/tools/pack200/Pack200Simple.sh create mode 100644 jdk/test/tools/pack200/Utils.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/data/README create mode 100644 jdk/test/tools/pack200/pack200-verifier/data/golden.jar create mode 100644 jdk/test/tools/pack200/pack200-verifier/make/build.xml create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionSyntax.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/TokenList.java create mode 100644 jdk/test/tools/pack200/pack200-verifier/src/xmlkit/XMLKit.java diff --git a/jdk/test/tools/pack200/CommandLineTests.java b/jdk/test/tools/pack200/CommandLineTests.java new file mode 100644 index 00000000000..fefefd2a860 --- /dev/null +++ b/jdk/test/tools/pack200/CommandLineTests.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2007, 2010 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. + */ + +/* + * @test CommandLineTests.sh + * @bug 6521334 6965836 6965836 + * @compile -XDignore.symbol.file CommandLineTests.java Pack200Test.java + * @run main/timeout=1200 CommandLineTests + * @summary An ad hoc test to verify the behavior of pack200/unpack200 CLIs, + * and a simulation of pack/unpacking in the install repo. + * @author ksrini + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +/* + * We try a potpouri of things ie. we have pack.conf to setup some + * options as well as a couple of command line options. We also test + * the packing and unpacking mechanism using the Java APIs. This also + * simulates pack200 the install workspace, noting that this is a simulation + * and can only test jars that are guaranteed to be available, also the + * configuration may not be in sync with the installer workspace. + */ + +public class CommandLineTests { + private static final File CWD = new File("."); + private static final File EXP_SDK = new File(CWD, "exp-sdk-image"); + private static final File EXP_SDK_LIB_DIR = new File(EXP_SDK, "lib"); + private static final File EXP_SDK_BIN_DIR = new File(EXP_SDK, "bin"); + private static final File EXP_JRE_DIR = new File(EXP_SDK, "jre"); + private static final File EXP_JRE_LIB_DIR = new File(EXP_JRE_DIR, "lib"); + private static final File RtJar = new File(EXP_JRE_LIB_DIR, "rt.jar"); + private static final File CharsetsJar = new File(EXP_JRE_LIB_DIR, "charsets.jar"); + private static final File JsseJar = new File(EXP_JRE_LIB_DIR, "jsse.jar"); + private static final File ToolsJar = new File(EXP_SDK_LIB_DIR, "tools.jar"); + private static final File javaCmd; + private static final File javacCmd; + private static final File ConfigFile = new File("pack.conf"); + private static final List jarList; + + static { + javaCmd = Utils.IsWindows + ? new File(EXP_SDK_BIN_DIR, "java.exe") + : new File(EXP_SDK_BIN_DIR, "java"); + + javacCmd = Utils.IsWindows + ? new File(EXP_SDK_BIN_DIR, "javac.exe") + : new File(EXP_SDK_BIN_DIR, "javac"); + + jarList = new ArrayList(); + jarList.add(RtJar); + jarList.add(CharsetsJar); + jarList.add(JsseJar); + jarList.add(ToolsJar); + } + + // init test area with a copy of the sdk + static void init() throws IOException { + Utils.recursiveCopy(Utils.JavaSDK, EXP_SDK); + creatConfigFile(); + } + + // Hopefully, this should be kept in sync with what the installer does. + static void creatConfigFile() throws IOException { + FileOutputStream fos = null; + PrintStream ps = null; + try { + fos = new FileOutputStream(ConfigFile); + ps = new PrintStream(fos); + ps.println("com.sun.java.util.jar.pack.debug.verbose=0"); + ps.println("pack.modification.time=keep"); + ps.println("pack.keep.class.order=true"); + ps.println("pack.deflate.hint=false"); + // Fail the build, if new or unknown attributes are introduced. + ps.println("pack.unknown.attribute=error"); + ps.println("pack.segment.limit=-1"); + // BugId: 6328502, These files will be passed-through as-is. + ps.println("pack.pass.file.0=java/lang/Error.class"); + ps.println("pack.pass.file.1=java/lang/LinkageError.class"); + ps.println("pack.pass.file.2=java/lang/Object.class"); + ps.println("pack.pass.file.3=java/lang/Throwable.class"); + ps.println("pack.pass.file.4=java/lang/VerifyError.class"); + ps.println("pack.pass.file.5=com/sun/demo/jvmti/hprof/Tracker.class"); + } finally { + Utils.close(ps); + Utils.close(fos); + } + } + + static void runPack200(boolean jre) throws IOException { + List cmdsList = new ArrayList(); + for (File f : jarList) { + if (jre && f.getName().equals("tools.jar")) { + continue; // need not worry about tools.jar for JRE + } + // make a backup copy for re-use + File bakFile = new File(f.getName() + ".bak"); + if (!bakFile.exists()) { // backup + Utils.copyFile(f.getAbsoluteFile(), bakFile.getAbsoluteFile()); + } else { // restore + Utils.copyFile(bakFile.getAbsoluteFile(), f.getAbsoluteFile()); + } + cmdsList.clear(); + cmdsList.add(Utils.getPack200Cmd()); + cmdsList.add("-J-esa"); + cmdsList.add("-J-ea"); + cmdsList.add(Utils.Is64Bit ? "-J-Xmx1g" : "-J-Xmx512m"); + cmdsList.add("--repack"); + cmdsList.add("--config-file=" + ConfigFile.getAbsolutePath()); + if (jre) { + cmdsList.add("--strip-debug"); + } + // NOTE: commented until 6965836 is fixed + // cmdsList.add("--code-attribute=StackMapTable=strip"); + cmdsList.add(f.getAbsolutePath()); + Utils.runExec(cmdsList); + } + } + + static void testJRE() throws IOException { + runPack200(true); + // the speciment JRE + List cmdsList = new ArrayList(); + cmdsList.add(javaCmd.getAbsolutePath()); + cmdsList.add("-verify"); + cmdsList.add("-version"); + Utils.runExec(cmdsList); + } + + static void testJDK() throws IOException { + runPack200(false); + // test the specimen JDK + List cmdsList = new ArrayList(); + cmdsList.add(javaCmd.getAbsolutePath()); + cmdsList.add("-verify"); + cmdsList.add("-version"); + Utils.runExec(cmdsList); + + // invoke javac to test the tools.jar + cmdsList.clear(); + cmdsList.add(javacCmd.getAbsolutePath()); + cmdsList.add("-J-verify"); + cmdsList.add("-help"); + Utils.runExec(cmdsList); + } + public static void main(String... args) { + try { + init(); + testJRE(); + testJDK(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/jdk/test/tools/pack200/Pack200Simple.sh b/jdk/test/tools/pack200/Pack200Simple.sh deleted file mode 100644 index 71b9611c963..00000000000 --- a/jdk/test/tools/pack200/Pack200Simple.sh +++ /dev/null @@ -1,197 +0,0 @@ -# -# Copyright (c) 2003, 2007, 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. -# - -# @test Pack200Simple.sh -# @bug 6521334 -# @build Pack200Test -# @run shell/timeout=1200 Pack200Simple.sh -# @summary An ad hoc test to verify class-file format. -# @author Kumar Srinivasan - -# The goal of this test is to assist javac or other developers -# who modify class file formats, to quickly test those modifications -# without having to build the install workspace. However it must -# be noted that building the install workspace is the only know -# way to prevent build breakages. - -# Pack200 developers could use this as a basic smoke-test, however -# please note, there are other more elaborate and thorough tests for -# this very purpose. - -# We try a potpouri of things ie. we have pack.conf to setup some -# options as well as a couple of command line options. We also test -# the packing and unpacking mechanism using the Java APIs. - -# print error and exit with a message -errorOut() { - if [ "x$1" = "x" ]; then - printf "Error: Unknown error\n" - else - printf "Error: %s\n" "$1" - fi - - exit 1 -} - -# Verify directory context variables are set -if [ "${TESTJAVA}" = "" ]; then - errorOut "TESTJAVA not set. Test cannot execute. Failed." -fi - -if [ "${TESTSRC}" = "" ]; then - errorOut "TESTSRC not set. Test cannot execute. Failed." -fi - - -if [ "${TESTCLASSES}" = "" ]; then - errorOut "TESTCLASSES not set. Test cannot execute. Failed." -fi - -# The common java utils we need -PACK200=${TESTJAVA}/bin/pack200 -UNPACK200=${TESTJAVA}/bin/unpack200 -JAR=${TESTJAVA}/bin/jar - -# For Windows and Linux needs the heap to be set, for others ergonomics -# will do the rest. It is important to use ea, which can expose class -# format errors much earlier than later. - -OS=`uname -s` - - -case "$OS" in - Windows*|CYGWIN* ) - PackOptions="-J-Xmx512m -J-ea" - break - ;; - - Linux ) - PackOptions="-J-Xmx512m -J-ea" - break - ;; - - * ) - PackOptions="-J-ea" - ;; -esac - -# Creates a packfile of choice expects 1 argument the filename -createConfigFile() { - # optimize for speed - printf "pack.effort=1\n" > $1 - # we DO want to know about new attributes - printf "pack.unknown.attribute=error\n" >> $1 - # optimize for speed - printf "pack.deflate.hint=false\n" >> $1 - # keep the ordering for easy compare - printf "pack.keep.class.order=true\n" >> $1 -} - - -# Tests a given jar, expects 1 argument the fully qualified -# name to a test jar, it writes all output to the current -# directory which is a scratch area. -testAJar() { - PackConf="pack.conf" - createConfigFile $PackConf - - # Try some command line options - CLIPackOptions="$PackOptions -v --no-gzip --segment-limit=10000 --config-file=$PackConf" - - jfName=`basename $1` - - ${PACK200} $CLIPackOptions ${jfName}.pack $1 > ${jfName}.pack.log 2>&1 - if [ $? != 0 ]; then - errorOut "$jfName packing failed" - fi - - # We want to test unpack200, therefore we dont use -r with pack - ${UNPACK200} -v ${jfName}.pack $jfName > ${jfName}.unpack.log 2>&1 - if [ $? != 0 ]; then - errorOut "$jfName unpacking failed" - fi - - # A quick crc compare test to ensure a well formed zip - # archive, this is a critical unpack200 behaviour. - - unzip -t $jfName > ${jfName}.unzip.log 2>&1 - if [ $? != 0 ]; then - errorOut "$jfName unzip -t test failed" - fi - - # The PACK200 signature should be at the top of the log - # this tag is critical for deployment related tools. - - head -5 ${jfName}.unzip.log | grep PACK200 > /dev/null 2>&1 - if [ $? != 0 ]; then - errorOut "$jfName PACK200 signature missing" - fi - - - # we know the size fields don't match, strip 'em out, its - # extremely important to ensure that the date stamps match up. - # Don't EVER sort the output we are checking for correct ordering. - - ${JAR} -tvf $1 | sed -e 's/^ *[0-9]* //g'> ${jfName}.ref.txt - ${JAR} -tvf $jfName | sed -e 's/^ *[0-9]* //g'> ${jfName}.cmp.txt - - diff ${jfName}.ref.txt ${jfName}.cmp.txt > ${jfName}.diff.log 2>&1 - if [ $? != 0 ]; then - errorOut "$jfName files missing" - fi -} - -# These JARs are the largest and also the most likely specimens to -# expose class format issues and stress the packer as well. - -JLIST="${TESTJAVA}/lib/tools.jar ${TESTJAVA}/jre/lib/rt.jar" - - -# Test the Command Line Interfaces (CLI). -mkdir cliTestDir -_pwd=`pwd` -cd cliTestDir - -for jarfile in $JLIST ; do - if [ -f $jarfile ]; then - testAJar $jarfile - else - errorOut "Error: '$jarFile' does not exist\nTest requires a j2sdk-image\n" - fi -done -cd $_pwd - -# Test the Java APIs. -mkdir apiTestDir -_pwd=`pwd` -cd apiTestDir - -# Strip out the -J prefixes. -JavaPackOptions=`printf %s "$PackOptions" | sed -e 's/-J//g'` - -# Test the Java APIs now. -$TESTJAVA/bin/java $JavaPackOptions -cp $TESTCLASSES Pack200Test $JLIST || exit 1 - -cd $_pwd - -exit 0 diff --git a/jdk/test/tools/pack200/Pack200Test.java b/jdk/test/tools/pack200/Pack200Test.java index 631b50709da..04dbf7327c2 100644 --- a/jdk/test/tools/pack200/Pack200Test.java +++ b/jdk/test/tools/pack200/Pack200Test.java @@ -24,111 +24,97 @@ import java.util.*; import java.io.*; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; import java.util.jar.*; -import java.util.zip.*; -/* - * Pack200Test.java - * - * @author ksrini - */ + /* + * @test + * @bug 6521334 6712743 + * @summary check for memory leaks, test general packer/unpacker functionality\ + * using native and java unpackers + * @compile -XDignore.symbol.file Utils.java Pack200Test.java + * @run main/othervm/timeout=1200 -Xmx512m Pack200Test + * @author ksrini + */ /** - * These tests are very rudimentary smoke tests to ensure that the packing - * unpacking process works on a select set of JARs. + * Tests the packing/unpacking via the APIs. */ public class Pack200Test { private static ArrayList jarList = new ArrayList(); - static final String PACKEXT = ".pack"; + static final MemoryMXBean mmxbean = ManagementFactory.getMemoryMXBean(); + static final long m0 = getUsedMemory(); + static final int LEAK_TOLERANCE = 20000; // OS and GC related variations. /** Creates a new instance of Pack200Test */ private Pack200Test() {} + static long getUsedMemory() { + mmxbean.gc(); + mmxbean.gc(); + mmxbean.gc(); + return mmxbean.getHeapMemoryUsage().getUsed()/1024; + } + + private static void leakCheck() throws Exception { + long diff = getUsedMemory() - m0; + System.out.println(" Info: memory diff = " + diff + "K"); + if ( diff > LEAK_TOLERANCE) { + throw new Exception("memory leak detected " + diff); + } + } + private static void doPackUnpack() { for (File in : jarList) { - Pack200.Packer packer = Pack200.newPacker(); - Map p = packer.properties(); - // Take the time optimization vs. space - p.put(packer.EFFORT, "1"); // CAUTION: do not use 0. - // Make the memory consumption as effective as possible - p.put(packer.SEGMENT_LIMIT,"10000"); - // throw an error if an attribute is unrecognized - p.put(packer.UNKNOWN_ATTRIBUTE, packer.ERROR); - // ignore all JAR deflation requests to save time - p.put(packer.DEFLATE_HINT, packer.FALSE); - // save the file ordering of the original JAR - p.put(packer.KEEP_FILE_ORDER, packer.TRUE); - + JarOutputStream javaUnpackerStream = null; + JarOutputStream nativeUnpackerStream = null; + JarFile jarFile = null; try { - JarFile jarFile = new JarFile(in); + jarFile = new JarFile(in); // Write out to a jtreg scratch area - FileOutputStream fos = new FileOutputStream(in.getName() + PACKEXT); + File packFile = new File(in.getName() + Utils.PACK_FILE_EXT); - System.out.print("Packing [" + in.toString() + "]..."); + System.out.println("Packing [" + in.toString() + "]"); // Call the packer - packer.pack(jarFile, fos); + Utils.pack(jarFile, packFile); jarFile.close(); - fos.close(); - - System.out.print("Unpacking..."); - File f = new File(in.getName() + PACKEXT); + leakCheck(); + System.out.println(" Unpacking using java unpacker"); + File javaUnpackedJar = new File("java-" + in.getName()); // Write out to current directory, jtreg will setup a scratch area - JarOutputStream jostream = new JarOutputStream(new FileOutputStream(in.getName())); - - // Unpack the files - Pack200.Unpacker unpacker = Pack200.newUnpacker(); - // Call the unpacker - unpacker.unpack(f, jostream); - // Must explicitly close the output. - jostream.close(); - System.out.print("Testing..."); + javaUnpackerStream = new JarOutputStream( + new FileOutputStream(javaUnpackedJar)); + Utils.unpackj(packFile, javaUnpackerStream); + javaUnpackerStream.close(); + System.out.println(" Testing...java unpacker"); + leakCheck(); // Ok we have unpacked the file, lets test it. - doTest(in); + Utils.doCompareVerify(in.getAbsoluteFile(), javaUnpackedJar); + + System.out.println(" Unpacking using native unpacker"); + // Write out to current directory + File nativeUnpackedJar = new File("native-" + in.getName()); + nativeUnpackerStream = new JarOutputStream( + new FileOutputStream(nativeUnpackedJar)); + Utils.unpackn(packFile, nativeUnpackerStream); + nativeUnpackerStream.close(); + System.out.println(" Testing...native unpacker"); + leakCheck(); + // the unpackers (native and java) should produce identical bits + // so we use use bit wise compare, the verification compare is + // very expensive wrt. time. + Utils.doCompareBitWise(javaUnpackedJar, nativeUnpackedJar); System.out.println("Done."); } catch (Exception e) { - System.out.println("ERROR: " + e.getMessage()); - System.exit(1); - } - } - } - - private static ArrayList getZipFileEntryNames(ZipFile z) { - ArrayList out = new ArrayList(); - for (ZipEntry ze : Collections.list(z.entries())) { - out.add(ze.getName()); - } - return out; - } - - private static void doTest(File in) throws Exception { - // make sure all the files in the original jar exists in the other - ArrayList refList = getZipFileEntryNames(new ZipFile(in)); - ArrayList cmpList = getZipFileEntryNames(new ZipFile(in.getName())); - - System.out.print(refList.size() + "/" + cmpList.size() + " entries..."); - - if (refList.size() != cmpList.size()) { - throw new Exception("Missing: files ?, entries don't match"); - } - - for (String ename: refList) { - if (!cmpList.contains(ename)) { - throw new Exception("Does not contain : " + ename); - } - } - } - - private static void doSanity(String[] args) { - for (String s: args) { - File f = new File(s); - if (f.exists()) { - jarList.add(f); - } else { - System.out.println("Warning: The JAR file " + f.toString() + " does not exist,"); - System.out.println(" this test requires a JDK image, this file will be skipped."); + throw new RuntimeException(e); + } finally { + Utils.close(nativeUnpackerStream); + Utils.close(javaUnpackerStream); + Utils.close((Closeable) jarFile); } } } @@ -137,11 +123,12 @@ public class Pack200Test { * @param args the command line arguments */ public static void main(String[] args) { - if (args.length < 1) { - System.out.println("Usage: jar1 jar2 jar3 ....."); - System.exit(1); - } - doSanity(args); + // select the jars carefully, adding more jars will increase the + // testing time, especially for jprt. + jarList.add(Utils.locateJar("tools.jar")); + jarList.add(Utils.locateJar("rt.jar")); + jarList.add(Utils.locateJar("golden.jar")); + System.out.println(jarList); doPackUnpack(); } } diff --git a/jdk/test/tools/pack200/PackageVersionTest.java b/jdk/test/tools/pack200/PackageVersionTest.java index 0cd9ca26453..344aadd6b61 100644 --- a/jdk/test/tools/pack200/PackageVersionTest.java +++ b/jdk/test/tools/pack200/PackageVersionTest.java @@ -22,13 +22,14 @@ * questions. */ -/** - * @test - * @bug 6712743 - * @summary verify package versioning - * @compile -XDignore.symbol.file PackageVersionTest.java - * @run main PackageVersionTest - */ +/* + * @test + * @bug 6712743 + * @summary verify package versions + * @compile -XDignore.symbol.file Utils.java PackageVersionTest.java + * @run main PackageVersionTest + * @author ksrini + */ import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -74,14 +75,6 @@ public class PackageVersionTest { JAVA5_PACKAGE_MINOR_VERSION); } - static void close(Closeable c) { - if (c == null) { - return; - } - try { - c.close(); - } catch (IOException ignore) {} - } static void createClassFile(String name) { createJavaFile(name); @@ -93,7 +86,7 @@ public class PackageVersionTest { name.substring(name.length() - 1), name + ".java" }; - compileJava(javacCmds); + Utils.compiler(javacCmds); } static void createJavaFile(String name) { @@ -108,22 +101,8 @@ public class PackageVersionTest { } catch (IOException ioe) { throw new RuntimeException("creation of test file failed"); } finally { - close(ps); - close(fos); - } - } - - static void compileJava(String... javacCmds) { - if (com.sun.tools.javac.Main.compile(javacCmds) != 0) { - throw new RuntimeException("compilation failed"); - } - } - - static void makeJar(String... jargs) { - sun.tools.jar.Main jarTool = - new sun.tools.jar.Main(System.out, System.err, "jartool"); - if (!jarTool.run(jargs)) { - throw new RuntimeException("jar command failed"); + Utils.close(ps); + Utils.close(fos); } } @@ -136,7 +115,7 @@ public class PackageVersionTest { jarFileName.getName(), filename }; - makeJar(jargs); + Utils.jar(jargs); JarFile jfin = null; try { @@ -163,7 +142,7 @@ public class PackageVersionTest { } catch (IOException ioe) { throw new RuntimeException(ioe.getMessage()); } finally { - close(jfin); + Utils.close((Closeable) jfin); } } } diff --git a/jdk/test/tools/pack200/SegmentLimit.java b/jdk/test/tools/pack200/SegmentLimit.java index 249e47b032f..f47207115b5 100644 --- a/jdk/test/tools/pack200/SegmentLimit.java +++ b/jdk/test/tools/pack200/SegmentLimit.java @@ -21,22 +21,18 @@ * questions. */ -/** +/* * @test * @bug 6575373 * @summary verify default segment limit - * @compile SegmentLimit.java + * @compile -XDignore.symbol.file Utils.java SegmentLimit.java * @run main SegmentLimit + * @author ksrini */ -import java.io.BufferedReader; -import java.io.Closeable; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; /* * Run this against a large jar file, by default the packer should generate only @@ -45,89 +41,36 @@ import java.io.PrintStream; public class SegmentLimit { - private static final File javaHome = new File(System.getProperty("java.home")); - public static void main(String... args) { - if (!javaHome.getName().endsWith("jre")) { - throw new RuntimeException("Error: requires an SDK to run"); - } - - File out = new File("test" + Pack200Test.PACKEXT); + File out = new File("test" + Utils.PACK_FILE_EXT); out.delete(); runPack200(out); } - static void close(Closeable c) { - if (c == null) { - return; - } - try { - c.close(); - } catch (IOException ignore) {} - } - static void runPack200(File outFile) { - File binDir = new File(javaHome, "bin"); - File pack200Exe = System.getProperty("os.name").startsWith("Windows") - ? new File(binDir, "pack200.exe") - : new File(binDir, "pack200"); - File sdkHome = javaHome.getParentFile(); + File sdkHome = Utils.JavaSDK; File testJar = new File(new File(sdkHome, "lib"), "tools.jar"); - System.out.println("using pack200: " + pack200Exe.getAbsolutePath()); + System.out.println("using pack200: " + Utils.getPack200Cmd()); - String[] cmds = { pack200Exe.getAbsolutePath(), - "--effort=1", - "--verbose", - "--no-gzip", - outFile.getName(), - testJar.getAbsolutePath() - }; - InputStream is = null; - BufferedReader br = null; - InputStreamReader ir = null; + List cmdsList = new ArrayList(); + cmdsList.add(Utils.getPack200Cmd()); + cmdsList.add("--effort=1"); + cmdsList.add("--verbose"); + cmdsList.add("--no-gzip"); + cmdsList.add(outFile.getName()); + cmdsList.add(testJar.getAbsolutePath()); + List outList = Utils.runExec(cmdsList); - FileOutputStream fos = null; - PrintStream ps = null; - - try { - ProcessBuilder pb = new ProcessBuilder(cmds); - pb.redirectErrorStream(true); - Process p = pb.start(); - is = p.getInputStream(); - ir = new InputStreamReader(is); - br = new BufferedReader(ir); - - File logFile = new File("pack200.log"); - fos = new FileOutputStream(logFile); - ps = new PrintStream(fos); - - String line = br.readLine(); - int count = 0; - while (line != null) { - line = line.trim(); - if (line.matches(".*Transmitted.*files of.*input bytes in a segment of.*bytes")) { - count++; - } - ps.println(line); - line=br.readLine(); + int count = 0; + for (String line : outList) { + System.out.println(line); + if (line.matches(".*Transmitted.*files of.*input bytes in a segment of.*bytes")) { + count++; } - p.waitFor(); - if (p.exitValue() != 0) { - throw new RuntimeException("pack200 failed"); - } - p.destroy(); - if (count > 1) { - throw new Error("test fails: check for multiple segments(" + - count + ") in: " + logFile.getAbsolutePath()); - } - } catch (IOException ex) { - throw new RuntimeException(ex.getMessage()); - } catch (InterruptedException ignore){ - } finally { - close(is); - close(ps); - close(fos); + } + if (count != 1) { + throw new Error("test fails: check for 0 or multiple segments"); } } } diff --git a/jdk/test/tools/pack200/Utils.java b/jdk/test/tools/pack200/Utils.java new file mode 100644 index 00000000000..e80b2aa85c2 --- /dev/null +++ b/jdk/test/tools/pack200/Utils.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2007, 2010 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.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Pack200; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * + * @author ksrini + */ + +/* + * This class contains all the commonly used utilities used by various tests + * in this directory. + */ +class Utils { + static final String JavaHome = System.getProperty("test.java", + System.getProperty("java.home")); + static final boolean IsWindows = + System.getProperty("os.name").startsWith("Windows"); + static final boolean Is64Bit = + System.getProperty("sun.arch.data.model", "32").equals("64"); + static final File JavaSDK = new File(JavaHome).getParentFile(); + + static final String PACK_FILE_EXT = ".pack"; + static final String JAVA_FILE_EXT = ".java"; + static final String CLASS_FILE_EXT = ".class"; + static final String JAR_FILE_EXT = ".jar"; + + static final File TEST_SRC_DIR = new File(System.getProperty("test.src")); + static final String VERIFIER_DIR_NAME = "pack200-verifier"; + static final File VerifierJar = new File(VERIFIER_DIR_NAME + JAR_FILE_EXT); + + private Utils() {} // all static + + static { + if (!JavaHome.endsWith("jre")) { + throw new RuntimeException("Error: requires an SDK to run"); + } + } + + private static void init() throws IOException { + if (VerifierJar.exists()) { + return; + } + File srcDir = new File(TEST_SRC_DIR, VERIFIER_DIR_NAME); + List javaFileList = findFiles(srcDir, createFilter(JAVA_FILE_EXT)); + File tmpFile = File.createTempFile("javac", ".tmp"); + File classesDir = new File("xclasses"); + classesDir.mkdirs(); + FileOutputStream fos = null; + PrintStream ps = null; + try { + fos = new FileOutputStream(tmpFile); + ps = new PrintStream(fos); + for (File f : javaFileList) { + ps.println(f.getAbsolutePath()); + } + } finally { + close(ps); + close(fos); + } + + compiler("-d", + "xclasses", + "@" + tmpFile.getAbsolutePath()); + + jar("cvfe", + VerifierJar.getName(), + "sun.tools.pack.verify.Main", + "-C", + "xclasses", + "."); + } + + static void dirlist(File dir) { + File[] files = dir.listFiles(); + System.out.println("--listing " + dir.getAbsolutePath() + "---"); + for (File f : files) { + StringBuffer sb = new StringBuffer(); + sb.append(f.isDirectory() ? "d " : "- "); + sb.append(f.getName()); + System.out.println(sb); + } + } + static void doCompareVerify(File reference, File specimen) throws IOException { + init(); + List cmds = new ArrayList(); + cmds.add(getJavaCmd()); + cmds.add("-jar"); + cmds.add(VerifierJar.getName()); + cmds.add(reference.getAbsolutePath()); + cmds.add(specimen.getAbsolutePath()); + cmds.add("-O"); + runExec(cmds); + } + + static void doCompareBitWise(File reference, File specimen) + throws IOException { + init(); + List cmds = new ArrayList(); + cmds.add(getJavaCmd()); + cmds.add("-jar"); + cmds.add(VerifierJar.getName()); + cmds.add(reference.getName()); + cmds.add(specimen.getName()); + cmds.add("-O"); + cmds.add("-b"); + runExec(cmds); + } + + static FileFilter createFilter(final String extension) { + return new FileFilter() { + @Override + public boolean accept(File pathname) { + String name = pathname.getName(); + if (name.endsWith(extension)) { + return true; + } + return false; + } + }; + } + + static final FileFilter DIR_FILTER = new FileFilter() { + public boolean accept(File pathname) { + if (pathname.isDirectory()) { + return true; + } + return false; + } + }; + + static final FileFilter FILE_FILTER = new FileFilter() { + public boolean accept(File pathname) { + if (pathname.isFile()) { + return true; + } + return false; + } + }; + + private static void setFileAttributes(File src, File dst) throws IOException { + dst.setExecutable(src.canExecute()); + dst.setReadable(src.canRead()); + dst.setWritable(src.canWrite()); + dst.setLastModified(src.lastModified()); + } + + static void copyFile(File src, File dst) throws IOException { + if (src.isDirectory()) { + dst.mkdirs(); + setFileAttributes(src, dst); + return; + } else { + File baseDirFile = dst.getParentFile(); + if (!baseDirFile.exists()) { + baseDirFile.mkdirs(); + } + } + FileInputStream in = null; + FileOutputStream out = null; + FileChannel srcChannel = null; + FileChannel dstChannel = null; + try { + in = new FileInputStream(src); + out = new FileOutputStream(dst); + srcChannel = in.getChannel(); + dstChannel = out.getChannel(); + + long retval = srcChannel.transferTo(0, src.length(), dstChannel); + if (src.length() != dst.length()) { + throw new IOException("file copy failed for " + src); + } + } finally { + close(srcChannel); + close(dstChannel); + close(in); + close(out); + } + setFileAttributes(src, dst); + } + + /* + * Suppose a path is provided which consists of a full path + * this method returns the sub path for a full path ex: /foo/bar/baz/foobar.z + * and the base path is /foo/bar it will will return baz/foobar.z. + */ + private static String getEntryPath(String basePath, String fullPath) { + if (!fullPath.startsWith(basePath)) { + return null; + } + return fullPath.substring(basePath.length()); + } + + static String getEntryPath(File basePathFile, File fullPathFile) { + return getEntryPath(basePathFile.toString(), fullPathFile.toString()); + } + + public static void recursiveCopy(File src, File dest) throws IOException { + if (!src.exists() || !src.canRead()) { + throw new IOException("file not found or readable: " + src); + } + if (dest.exists() && !dest.isDirectory() && !dest.canWrite()) { + throw new IOException("file not found or writeable: " + dest); + } + if (!dest.exists()) { + dest.mkdirs(); + } + List a = directoryList(src); + for (File f : a) { + copyFile(f, new File(dest, getEntryPath(src, f))); + } + } + + static List directoryList(File dirname) { + List dirList = new ArrayList(); + return directoryList(dirname, dirList, null); + } + + private static List directoryList(File dirname, List dirList, + File[] dirs) { + dirList.addAll(Arrays.asList(dirname.listFiles(FILE_FILTER))); + dirs = dirname.listFiles(DIR_FILTER); + for (File f : dirs) { + if (f.isDirectory() && !f.equals(dirname)) { + dirList.add(f); + directoryList(f, dirList, dirs); + } + } + return dirList; + } + + static void recursiveDelete(File dir) throws IOException { + if (dir.isFile()) { + dir.delete(); + } else if (dir.isDirectory()) { + File[] entries = dir.listFiles(); + for (int i = 0; i < entries.length; i++) { + if (entries[i].isDirectory()) { + recursiveDelete(entries[i]); + } + entries[i].delete(); + } + dir.delete(); + } + } + + static List findFiles(File startDir, FileFilter filter) + throws IOException { + List list = new ArrayList(); + findFiles0(startDir, list, filter); + return list; + } + /* + * finds files in the start directory using the the filter, appends + * the files to the dirList. + */ + private static void findFiles0(File startDir, List list, + FileFilter filter) throws IOException { + File[] foundFiles = startDir.listFiles(filter); + list.addAll(Arrays.asList(foundFiles)); + File[] dirs = startDir.listFiles(DIR_FILTER); + for (File dir : dirs) { + findFiles0(dir, list, filter); + } + } + + static void close(Closeable c) { + if (c == null) { + return; + } + try { + c.close(); + } catch (IOException ignore) { + } + } + + static void compiler(String... javacCmds) { + if (com.sun.tools.javac.Main.compile(javacCmds) != 0) { + throw new RuntimeException("compilation failed"); + } + } + + static void jar(String... jargs) { + sun.tools.jar.Main jarTool = + new sun.tools.jar.Main(System.out, System.err, "jartool"); + if (!jarTool.run(jargs)) { + throw new RuntimeException("jar command failed"); + } + } + + // given a jar file foo.jar will write to foo.pack + static void pack(JarFile jarFile, File packFile) throws IOException { + Pack200.Packer packer = Pack200.newPacker(); + Map p = packer.properties(); + // Take the time optimization vs. space + p.put(packer.EFFORT, "1"); // CAUTION: do not use 0. + // Make the memory consumption as effective as possible + p.put(packer.SEGMENT_LIMIT, "10000"); + // ignore all JAR deflation requests to save time + p.put(packer.DEFLATE_HINT, packer.FALSE); + // save the file ordering of the original JAR + p.put(packer.KEEP_FILE_ORDER, packer.TRUE); + FileOutputStream fos = null; + try { + // Write out to a jtreg scratch area + fos = new FileOutputStream(packFile); + // Call the packer + packer.pack(jarFile, fos); + } finally { + close(fos); + } + } + + // uses java unpacker, slow but useful to discover issues with the packer + static void unpackj(File inFile, JarOutputStream jarStream) + throws IOException { + unpack0(inFile, jarStream, true); + + } + + // uses native unpacker using the java APIs + static void unpackn(File inFile, JarOutputStream jarStream) + throws IOException { + unpack0(inFile, jarStream, false); + } + + // given a packed file, create the jar file in the current directory. + private static void unpack0(File inFile, JarOutputStream jarStream, + boolean useJavaUnpack) throws IOException { + // Unpack the files + Pack200.Unpacker unpacker = Pack200.newUnpacker(); + Map props = unpacker.properties(); + if (useJavaUnpack) { + props.put("com.sun.java.util.jar.pack.disable.native", "true"); + } + // Call the unpacker + unpacker.unpack(inFile, jarStream); + } + + static byte[] getBuffer(ZipFile zf, ZipEntry ze) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte buf[] = new byte[8192]; + InputStream is = null; + try { + is = zf.getInputStream(ze); + int n = is.read(buf); + while (n > 0) { + baos.write(buf, 0, n); + n = is.read(buf); + } + return baos.toByteArray(); + } finally { + close(is); + } + } + + static ArrayList getZipFileEntryNames(ZipFile z) { + ArrayList out = new ArrayList(); + for (ZipEntry ze : Collections.list(z.entries())) { + out.add(ze.getName()); + } + return out; + } + + static List runExec(List cmdsList) { + ArrayList alist = new ArrayList(); + ProcessBuilder pb = + new ProcessBuilder(cmdsList); + Map env = pb.environment(); + pb.directory(new File(".")); + dirlist(new File(".")); + for (String x : cmdsList) { + System.out.print(x + " "); + } + System.out.println(""); + int retval = 0; + Process p = null; + InputStreamReader ir = null; + BufferedReader rd = null; + InputStream is = null; + try { + pb.redirectErrorStream(true); + p = pb.start(); + is = p.getInputStream(); + ir = new InputStreamReader(is); + rd = new BufferedReader(ir, 8192); + + String in = rd.readLine(); + while (in != null) { + alist.add(in); + System.out.println(in); + in = rd.readLine(); + } + retval = p.waitFor(); + if (retval != 0) { + throw new RuntimeException("process failed with non-zero exit"); + } + } catch (Exception ex) { + throw new RuntimeException(ex.getMessage()); + } finally { + close(rd); + close(ir); + close(is); + if (p != null) { + p.destroy(); + } + } + return alist; + } + + static String getUnpack200Cmd() { + return getAjavaCmd("unpack200"); + } + + static String getPack200Cmd() { + return getAjavaCmd("pack200"); + } + + static String getJavaCmd() { + return getAjavaCmd("java"); + } + + static String getAjavaCmd(String cmdStr) { + File binDir = new File(JavaHome, "bin"); + File unpack200File = IsWindows + ? new File(binDir, cmdStr + ".exe") + : new File(binDir, cmdStr); + + String cmd = unpack200File.getAbsolutePath(); + if (!unpack200File.canExecute()) { + throw new RuntimeException("please check" + + cmd + " exists and is executable"); + } + return cmd; + } + + private static List locaterCache = null; + // search the source dir and jdk dir for requested file and returns + // the first location it finds. + static File locateJar(String name) { + try { + if (locaterCache == null) { + locaterCache = new ArrayList(); + locaterCache.addAll(findFiles(TEST_SRC_DIR, createFilter(JAR_FILE_EXT))); + locaterCache.addAll(findFiles(JavaSDK, createFilter(JAR_FILE_EXT))); + } + for (File f : locaterCache) { + if (f.getName().equals(name)) { + return f; + } + } + throw new IOException("file not found: " + name); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/data/README b/jdk/test/tools/pack200/pack200-verifier/data/README new file mode 100644 index 00000000000..64278ab13d6 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/data/README @@ -0,0 +1,45 @@ +The files contained in the golden.jar have been harvested from many +different sources, some are hand-crafted invalid class files (odds directory), +or from random JDK builds. + +Generally these files serve to ensure the integrity of the packer and unpacker +by, + 1. maximizing the test coverage. + 2. exercising all the Bands in the pack200 specification. + 2. testing the behavior of the packer with invalid classes. + 3. testing the archive integrity, ordering and description (date, sizes, + CRC etc.) + +Build: +To rebuild this JAR follow these steps: + 1. unzip the golden.jar to some directory lets call it "example" + 2. now we can add any directories with files into example. + 2. run the script BUILDME.sh as + % sh BUILDME.sh example + +Note: the BUILDME.sh is known to work on all Unix platforms as well as Windows + using Cygwin. + +The above will create two JAR files in the current directory, +example.jar and example-cls.jar, now the example.jar can be used as the +golden.jar. + +To ensure the JAR has been built correctly use jar -tvf and compare the +results of the old jar and the newly built one, note that the compressed sizes +may differ, however the timestamps etc. should be consistent. + +Test: + Basic: + % pack200 --repack test.jar golden.jar + + Advanced: + Create a pack.conf as follows: + % cat pack.conf + com.sun.java.util.jar.pack.dump.bands=true + + % pack200 --no-gzip --config-file=pack.conf \ + --verbose golden.jar.pack golden.jar + + This command will dump the Bands in a unique directory BD_XXXXXX, + one can inspect the directory to ensure all of the bands are being + generated. Familiarity of the Pack200 specification is suggested. \ No newline at end of file diff --git a/jdk/test/tools/pack200/pack200-verifier/data/golden.jar b/jdk/test/tools/pack200/pack200-verifier/data/golden.jar new file mode 100644 index 0000000000000000000000000000000000000000..53b77c41dac4f10e09634c63f3264b693c177dd9 GIT binary patch literal 418156 zcmaI7b8u!s`z9PdvF&7HXJT{0C$??d=ESyb+qRQQCbn(kn|FV;U%k73?CCn)eY(4D zRGn&E_to-1a0qk|=zpo!@RAqgzaDfD2oM=jWg&V=SusW#Az4W=Q6*&t8L_Vk5RiMB zsYz)dJ;OW#ke+5{YPM04=?}}!p(BHo44tIRyh{ZHOxh{V5S}KLIYjCa^@}M5)*+S} zW@^z9CawDs`MJfNtA-kWi2;0( zi}jK+%aKq_jgdhj56F&+&)+FciYZb-5cG}p4gEpH0H(y_Lw#WX%OdV~a~<`6`b__y z7D4~(HFUACHfFFeo}8kUq@Msz&C4`29sG|FpgSrq(!U`H|A&wViLw8~ExLcbMs_xg zPA;~LmIkf{j4sX=*8ggbjP?dbR*WM5aaGOH!r8>}f85U2fYDD{YW|XC9?wnxFn~dB zAqGVm3`Sl>0h^Fzf(Qm_C?7OVfG1;dFa`Tz+iY8Aqf;rbRxzYAZ!Dz5j!2I zx0-I?kQ{GP5VxN6p|<9?I;FOp_IJOn)@^!^K0HD#%p)cQhd^9HbDcsdc zeN`Q7JAd{b?=sTt3T$kA3Ant$U4Et}e5>9GR7`(++6#PH?tb&8{T}3g(ISi~d-ylB zY~odsvUg$YCvZoo*w}+iOIym3H-R?g>8?$1W9{CZOwXEG5|krZH|1d_k)Mz*sb@n4 zHu*tmL2n!+kW^F(a5PDbQ)MjC1D|Y2K1jzU6aqh+h{|DEJSW0DKu>!3i@b1ylZsM< zc`fMCu^~Z`+{hgMmWI0U*0^dW`Pd+pe18LajGD%wD*?uX`KKJkX+ggSv&K4ghkIQ@ zxo3d|A(x7*YofFovxsEz%=DivI`UF#E}d~}z7c%jQAfQ_Y@5yztwvtaTtWz;2AN6< zUCRM{Kr=b=Ds~J2LXmvTc6EaoTjdgoL+zKA2u7y2mN!Oqy*`>(d<15$pVKfis-B2G z0P8)oAVnF@@reDkKp{W}0~Yd!De}(TI>OJwDN77Y+h~|j<;>!K`(a+asn#a88gX>G$-tqNh6fO~q40X`Wu`S~RZr@?&lDXa*3xUfty9Kfc9kyW?8yJacYx?#X- z{BOH;hxJ3pO+}T8Z7M!_GR&eTkNz?{Bc;3;`|87l34JQ;O!73Xwa>;9_L&vCDVhK) z+qRg{6CDD0NK4I34#1D*j`Tcz(WoCOFPc+n6l51Ef7Bb2@~y_1M+QIcKLaoee?V!G z_N(iQ8gU9=>J+Qc@XSuBgcy!r-3fM+o1u=q=BpcL>IO>xsz(c_z#Yr=2G$v`Z`sZLFfd*^aGte zC1TYR{v*NIG9-M84;tnfzVIR64Xx3kl%LW7IUnsnHHx3UkUT+ItVV+-$L}a5Np{=8 z!9W8YKtOb+eWU(d>o8f!m?d88&S*;(xs|ncc%`*=^f{e|)xM)P36-qPoTeVpZ-_JI zSHGgFbK`D$yaQ|CO#PvDzO6uibc^*+b;^rPIU1pUUi{(#9Gh|+2exI}BCfObfi3L2 zj|{m}>U2}FF0Qpj(jz>)R$#;UNO#-p2qEe!oy6i$~~DOP+3x1b>)THwz*iPg-nPA zc2?FDiF7ihdY~&=OHnxcZ#gPF|KHGrbaHk=MNvk1&X!4v{-G;C5t>=J;LJ5^!=Z5d zSVE*t`!9y(Lny~|a)G8p$NV$336&OYdLHX`v6|)sZY^K3rV+UVPdpp@E zr>R)yX|+Kwr{U0o9O?x7E~@G}J)?faK6_kgt^QDh%Y0w7%Xr_UYv4xOM*GJYjmBtT zLx0LKww$9xPATmeTuPVuBoOvwQVMaLwxz4$rkkR6-BEoUPim{^c%0D~Yf+Tr50Da@ zCZ2HRE(P}j$3u{*gSHn)i3;3mIV5m-;u^x&BRa>uFS=F?TDSsRd))2OJ%Rk} zEuWzPU##N@!NX01p{ZE4q&ao+PB`r^3Ew$S#Z*|whOu)wd!|13RM&#+UVy6%QUKFT z%PITu(xaY+oBEi?4FgE^ecv2hME*?Nc^4Q!2A?$M0bJI-fLvZ`+lB}?_rn#Nm0$+{qIy(?_n=FXX5b_ZM zc-}j!Qh&y|ltnjRY{~T~Q#~(c+qr}VgY)T_@hCgK$POi<^(WUP1kpQlig^MXV$F*`Wp4*9KDUzF@YWc5zsFOSGZgyK_^?jkKVRCIbTU-}IZIwbHi^Iag=^}y8d5~(4nP0)EDQhqO0AD)^HD`jKQV~Hty|qwAYtTfj^x{b@M*D z9*Q1F-}eqe8o90EH9FK0QuNI1B^WbP^Rt3bl~Nj~E($-!qmRnfA!8UJn>|#XefV%v zoATwlHwdiY#bj_>4|H<2oY8Qc6kCf++W$BeR@0wyi+O{`d*v=NQK|#aZL*rOuo9A+|4WV@$)ZA5H~trHEkF zKKD(9MRduFnRASHA!iw7PHigRaSC%M@X6xef8v{C%6|%QS-=`vhHqotbGQ=nq>F@@ z^0)IrP$jE#lOGOAE9em89`LX~mT93B!d37*@}ABGdE)a2Qg1Wzc6ezxYeSH?p{EMn zh#Y{vrG$OOB6%;~McZliY|BhSbz0aBOqX2pcJTr-RzjPw-#E0nkqmy8*X00A> zLfsMyE@$|RUHM0DX0dI#wmp%wf~U07#dIOIb(;{}xN}|Giu*!YH(r3iE=O&K>6%*D z>8yqI_Pi^s1o&#N&n^XgS=;>WPJ4g=;0vO-;iz*`#q@J$J2ApKYv7BlQEfhi-f7_v zJHW34FB@)HKHqt)7Yyuo`5#6Iv-ANm65C#LoUki%RbN+KxB z91WB1(~ykJiR}RQId&$5zPwIc>7yo0_lLRqIfJT~taTSE>Z>+mYH7b6$EEFV`J*I) z{!G$k`uC*${vpi_azIRLm+zba&l?yxYBC@;$g^r*WJD^5Psx3BGx9T{ zH}M7Uayn+;xE3hc(paWEi#dv|b~vY#wk;X+%*kb9gWVl$73$V(dg0`SV9LfB3!4-6 z+alYEVFxCl96wu%MUL`}1zML}jz6)nafJur{HxuVH!qO zAi(%o6V{3JhowWD1tK}AS<4-KBW+vfp)AEqdL9XdRhGy}#H*CZ$q33nyaBhJO!Zm5 zJC;GTo$izo6A?NtXu3lA7W-*+EVq{o2Sk0p0(nlkYG!Q`%MTr-bM~ZuZAxbMGJk}?}(B#XCPJmMrby>uPq z_-vH(>fF(qRm1H^gxO@ESaD77Mot+<)HlE;Nrj05%6G3UT!*AGtCK@+qoLU1Hm6!1W^Dg{p+>W_(>$T#Es=zuO) zL;z+Er;h5lHdhJ13F?~eE?L7b-ga>X5u>%dr0u>`GZoy0CjUE5?7s8v_;dEMw(A|5o`K=LOswpvT z4X#zHD_kzBJj+zevToI`k#1b|*@Nv1P0J$c6N1@dJBXP1Jn1jj1+g2^yL{% zK{~SJ+k$cXoS7X~UNdN%&-@Qk&OTl@1!89p_=&g?O|!Ypl-aEhh^V7tE;Ny?a3&8} z%a{HB^oISfxNA%UV<#M{d^M_! zk;5|CaM+7$NVbNmOue46xc_d9NH~zmo=6oS3XhJnIK{9!RoLUx+vr^i@K5s!((*!o z)}lbV2b=mP?cQOR)EiHY=q|JDji;4~YLhn3vO2|b?e4@%eEG>H(6G+fV=MgH-O8ve1QbDZ@1~vx>Dl@yNqH`BXRRC{c#qjPN`Rxc?&b zquE#>{Pr~4X*vURJkq%TbPBH|%WT(0d(st+&k0JXI~1;#@;3)lz}D;&XewUw$6XTe z#OM4Le>C|ejeJff&P@XHuifZ7Gm?iR+Ob`zmRQ3l*E)O4bED7i%Ry_NI^8|{Z-M_H zHux)gMFGelAmOC{jo94$U(g2S{~Ox)0k*e&&Ks6H>_}7S?K4k;^tP#)PL64-PQ}kA zNI7B0z-zGKtN^UT0sJoIjM_B!^ng^a8`zJEa5YbTdf(}x%3oQPqH9+0U8>MJW_ynf z4+s=n=6f;}zMFlSG8eak;I?cIVSQVMJJp2hq!$|$!M0e`S4_9EXxGdRrQzGi4Q^h< zfzN4elS8^lTanL__tV;4L_}l5P`CNo-q=LQec}+G3TQ+uw+^SilS6iRD`q>k`E^U+ z1a0CemmIe|fU8GAcsq_87$oQYOA3B!^k)xyfgHO%rNCPqiyZbF9JJiWzVF{1w*+Y0Y`1)9enxxm6kp3j+vzU> z;k_KUNEBc5Ljvh9W@rMex1?y_hI@K4ADQ8P9JfRiUyDP2=`R8lzSet`6uy>wy)wM| zdqQYPFyi2ZhLwl(lnG)il4h6jlf8!cdfF+IcO4N zIK{alM;sh#(n7~uVccnin)uM;5%&}5yG+*QA_t0S4vRpjGlH#{B+~*`@%m}S5XnU8 z3~R+=Nfio}Ol6}5^u&@3*6E^CifJZ`5=rq)Wvd0c#F(F_>WN?!1ZE4j6w~Y$F_KO) z8D@&vlJ_#2`g6h%mHD>)3#Uq@IXw*qE}s(^rHr*!JN+hR5FAjYO;DsZO1S&;_C=SR z!CjEq@#?(T;ko$M*N7xZRT}XKt-M?-b(SWB1uB)9vU=Ujp#Jh*{CI~7K}iiiWS~b3 z^UZmu&KcoH1M${H=%L#X`+dAQQDbI>=4dFgqw*L}tPMtBQ$k4dF*XVCMCO;krqBrb z?IH4;eoWp4rv1f6V$W^oT`At-GIrEI5Hz3r5rpY9v^%Ch7Y?9DDJ?3#NX7Ss*Btpq z;f-uz59>g81JH|)*~Nqg-p-xLi~rrLtKx4oe5ufQu@4={R)UCZ-aFm9eaTR-i?&Eo ztI|pZGmnE2gM;M3e_9v5b=j?f%qAenm%ukLOo60CAYztCTE~bQ5e~$SYN~P`5>!vh zn>9(tS(`L z7w&@D&*0&)G~7f9Q6=eP-@FzkDt!_#I|h zes^8S!z=0#oL1TL@N6U#bkm3DAV}qps>jrvrOb_7wd|fylFc^?k*f)?pr2LP9{QAB zMYRMMSe(!l163*3itLy?~`#dvSADTu<@;Y2U*xXJzJz zehin(uARe6I2p`G|DCpy9zR1Lf2N=HY-;4jYxKw_`Cu(?`FP|?!VAIG{uW5~W?wdR zR=q3lHMu*7ML8cPLO#bMd@GZU!(wqVsZ@anNEWm^QX&nq=6)MMzaPv~Jc(bn*XmHk z*}Se*IH?Xg5&5U4;RxOjJ9AS2+p%midiv@&Jl7w-5mPtZQ0ZS3-*Ez?`|Ol_40uU* zZH*m+`R=#^)Ph0+!2DRgjUQ24TU@_LPZ<_-ly%t4R$-2eE}%rx-B|q`|KrrcM~VFV zoZxZZ48|W17!Wh_#kl;lfBVZ9MJ_d4b>p6ALzuU?iLczjf$vLzs0cxJrpTGJqE=kJ z=b8mGzU9Do*yr>SOVpPr-+80k$@d@cF7*h0M#7*`PYY5uygz%z$jy!er^W2t!@x`` zI*kFGEC`9O05)Cj#5JxUDH}|r88Lh6*(b#?>9gB1vZI=1{m!Z5<)Q(MLh&-RfE?~^ zppJ72t3q%?KF-+3l|4@wfN_}Ws@QgI>KB(}MSK9g?wVAoqi<&U9a-|PJx!w1GueZZ z1Zvj?&P6fs7mCSO5w&z>I)CAWz~c$El9_D!&m~}LM4GLcTQ?`Tndd33Gfru6 z&{?&e9a1yo`gwuPW`cK-6o64j_SkWpSzu)kiGl*$xA){7Hu;?^J!6j><-)^g(VM~u z?VR*ho)y(v4?WuT?#VO6vQ(3D$IQ!s=anWqjOmBZpsMSrlBe7mYvV}VX4?W#4~@Ld z65rLi-EmW8(Um<=ZdsB%IXgnX?|RsRa}%gWO`4#s&M?(0q~>*s+p!G%JO8My>TwF_ zh@S*!-2A0?j5`@A4ixrcd9)vHVM0e@8x^7da(P7fGA->x(TMuhe^N}~3^(vH!ZN6% zW8L=^oqwdy5!Rs3)40_zea(bni9)7##J~t6mL0sES%Vb+q{_HS5mBMNdAf~?Fq)p_ z*O0KmC{=qm+95o>yKM9Irv&;{L4=-Lso4miW)@6MS@$a)WKhlM5>abTV;b1HNT3}N zAAFB!$Ki2W@Y0P&}P*_w(84@dsC;V1ALcj5y zOp@K2p$6k0Y@Y03Mmb(dv%eY%E=j8H-or`_()}>J6QDZR_Y*&Kvg_Sl)S4Q(&?8^> zGljocuk9sxwz`z1NOy0UhS9#cpsMs_n#29e`v~vtR>`Vdxe|E(s8W#4r~+^GfRu0; z&!dI?5>Y^6aaBl?qCu1zJ8tEG0Q&-HZ*1m4$e2*We5!jKOtf*b$U1e)x=Y#_l?2ly zecrTsjBt(K44L`o+B80!*AYo*ry^V+vJ|=zHEeiqF#E@hGk17ldY`6bSBsq@1oIsO zArM*Ol;Y=g>Z`xr%wL*bZrw_C)?RZ{c~I!dV&!$;h63I$#r<&B30}s+dVf94-@}db zXkJz;J?is#berz_@_*3z>g(9~>wmyJhOLPd3`eYxJ$c4tHSqlOuy4_&>9`aUb#@mT zMf3GDbf260=r=t^hmriXYfn~Ep-gOD+{h~j%fA)7x}I8&5xVRhPThisD5x;+$2qXe z2N_3P&lx>5*KrM(pq?XUsD7DRbh}E1Spt`!@m-wu0*|^ESsi`}luKV=mq(-?Rlss92Y8RzzOdGilR(FpA8YO54NEVBBfH%C`)8`Pz~KfR zb`VPN^ct7bN9k|gO%z1q%XP8}x?Pr8zdJ|SS5@bRW57YxU9G7{C0<%KBDjyDaL*HQ-BXOwHzcCVm@ zVzd!k5MbccC=f(zcf`gL9Ci7i)~d`*yCw#+@r*~f|M&0t)d|pa9G#(Fw959=vIKFy zv*76Z$gt48#(`AG*4vcg+wq=lLVlJ5vnL9KLw(78n%))W+Af+T_;LIv;jgP2%y5s0 zQF52c1=;nyh)pF_52^c>qbKBZwN!c(G+jLcKFW0SoFg*m)yAO5@h-VyKskKAxlECj zc?|ML*S>%3mO1N`HKkGYLE-mmzdYrkWAb+$q|05f91mAeb=D*$D!Yq6fPeN|VDjC} zFX!1W=9 z2%jynD9~dlPrUm9oA0XFZoakqk9g;5+TSW&?s~T5H7U6QKS>R{WYF@JL8{#F~2^k|F$#8$7BItGD zr>>nE$#MtB7LoR`-pt`=mJa0D)G%v{Q&9YVS@svQ!bM_NufNgs=Mdriccn zin%zyPh_z)$()9A!k=L#ftvi0;Z+V`KVO#xZLnw71);=c6kTDk!pS^Apt}m=EaGai zW)=q|<_u6mXs$5a!X-3j(QU5Ya2G2}x6lfzQJalt$4-^vdVC=*oN(b;Rc{#h7rYGL zcMx`5bC$(AjTuMef|gIvre43t8a_FbnV+Gbc|R9wQ7vibug;B)%Y8*nX3M-)j^f}! z(=V&CEQlu{{Zr5f7uI!p0KPyF#Z?qLizd5NDKe`1X zHJw`2EVk)QnYD7G?}9=+WCK4>8PCena1^PsU;Zp6YHXS4hP&Baw@1klK!9$XwK5;V zG4YNO!G7Mb{v*TOi|w|CdlRt@RF<9knG zbY#Q2GlDjeCmhLkf$*Yybj@0bxBUlISVDe6j%y68r+KAy@=O7XMYPgil8k9XArXxI zBWwG3;K4#6i<7u-FVHc;Eo7v{0)v#Hql&Be03!I* zeZ3UaGKdSnY4beO9+q^A@k+0BZb7FbAJU8j{pW=$3Oj*%)`E)naO{kw_5*RbR zHDr<=KmNr7mv0Epsl^F}=#W}MGEqhl7iRBNLv^x|vNr6c3Bf6uv@y<)D><`1#7tCO z?Qj^rm;&yTAnWTKj?29bhU?@CV6lsAjj9^68xf$)zp&m~EPpSDCLCt#Ch$sPtT~#D zX^PudO0*e=OP|-X99mTC?rwxHqwf8U%&p4R)63=aJ=r*199z$EcuP@(=i^+I4b|qy z!7a$2-kq`CB59;unhtldiy}AFwGj15IVqXJXfB+(C>nhKWU6K2EaD^)KSxe&FvrH z4i_KKD?9Z7j84nTXV*QhVo}+^3=dcjl^d4)B#d$!?L)e+2LaKRj|owS_>H-ZqVBDi z<*GOEOn$QO+ul_NNrg`mg1U_da%X}q5_8Zw-$x!hm)V7Pqi{tWv$tFgpdag>=LARy z-fta%i#)1}(a=VePehPP;ZMO7U&F3EaFo6=B3AaoZHK<|#)Eg_X${hcN%1B!c$vyITiD8( zC~6h7@)GPEdSnZvg(nTYgnO-s+9ZrrBY= zdoO|)p9WXq+IlY!V?OnxQ(*1rG)}(hpTz{^0%sXihJ2(y#DZ^Q$<3>L z>85QNA76*!1O#?7C5{skP&L6l3)>{>)Zv*v_Wx{{uUb7O`ODz_585?aRbLz(KO21p zPc^N|{9%TtQByF)K4TDlu%aq3rIJb0?NL$6%6X&7crBWv(h4{7b2?aXn5h>D^S3-4 z!RAAMfjExkEdv!{G0O%ar_eSyCI6b&&8==3>;WGDh+4L+s6eB2&J1OfG~xt*g?bY_ z3{&ahsvEfERGeDZ=G~I>5&Ve0^16Jd6Ib}R0jFv=6Qk1~;UJ_)qs_~JA+}}%lCYDw zkDt|sk(HHvl~ZzM3=8FB0T}Ocu!>Om^o*EghBrG%w_XQtlDM>C<;*J=-qwLN@jQ@0 zvA%*p33c~0utvdS$B{L_QeG`_lVFAu;O%fs0E4A6aHHQp*AGTWcc6#zO_#d`zB!^DQx~fZKrPGS))HH2L_{ow{Q4nfs}#lRXrZ86Wc% zBF%I`QWW(!!AT3jiaCMN-9MCe&-J?^E5t0e<8`dO-=l$H{0tB*PdKj9b)OpbRG&;N zrDRQ;WDfm zSAuhuLP{rF@eFmVrO~63eC4tKshJvM?2@awxMzXDen^*- zwFAnv1U=;)JB#S%F;H$Vu^_EB$y2DW)G#sD2#rN`A~NMF0PeX@l&`P;!jR1gCKi(- zYQ71M)4x$PWz{68DHb8>1j9Y0MY@O}zleZj!SPvf=~Jq&1*mS{NyK2(f63pLwcoJc zNFGuA!&H(CvsX$cT5dcyKdzuiczI0~Y$1M93i&kTP0)j#hV(K%U0KXjo_Z?JF!kCm z>E0jj)Z`81>=eUOTxYxZ`S2_PZ&OU*82fWXx83om4=0EnM|ZCWsY@R4$^-`6A8JSy zrieY>t?*&TO37}Nk2%uMi;Wa%0LD5HKL>$kz=sc6DumG_%r>NtYaMV}M_}6b%7n%c zSh!0Y~E`94}5B3po{S5}($*n_2Ny&$q zD{X4HM%fRNW7+1IUFow}Hy`BbpD;8W3zjoWDL;4N&f|ZXlKJFV=x>2bkjf zV@~W+$;0D~g)&74y5b@cC?lY71Gvi(JIu+mL}ieRiHFKC_MCB?7y0Va2}9{0n5~P| z^zlBiXG-)2Ts@&?^4)g5J&71hY;*s6ss%|-3o*}-e(jt114hwY11 zk@OaEcWEx2+ce<@*)*ZFD_#0s)-LS}U9r4%ydZ3sd%*eDlD-XDNF)SsF!HNK?0)!Xp1r;L5ik7~P6Z+JHiU!?9b-#8!g-)vnq-+-+-KRAKq zZ)m$SzkuAcupKi7#;!wL7y-BFAicJdK1Q9#o0dux-@`M1H|^}3nsYk$S&Xpl&Xxfp z9ksr&8oV7+HKdOv7pRZvB{)AfKG<)2AEK{QPw=n9HyG~=Pb_|iPaJ+1ANX(APdvmn zANw&rNWexTXSD$0csDlNv*d|mZbT^0Z$dY$qs~@?{C8eDN4@BjcL1HMK%mLhCg(yo z)@WPqAn69Ji*5 z)2hM3Xv{2=tdLRcP0n>rq+@Kg9qoPGEVGROxTtsg{Q24a+1nQ@-Y<&S-r0Sk+ZRx6 zWB46I-oWe<0#$^%@nh_pf&kwn0r1f~KYz3~J`nN^U)W)&9SQJJ0thA3-C^*BU9DU& zLO*CRZgtKpC3^yYu8r)lsHl5nr!#bz2f{!{G0_if(?t+3lhZoU^b!$)`TpDS+xjwj zP((q(ZG=8Lsu!>mkX$f=z>qWBzyNVVe_=vG{n?psUgzbRcsj0W4tv94&@q#)Ao+cN z%qbDRYqT=cGb7;r(XJ5Ha8d_ie--+=IHm^MmiAwg74w@I!w|*c18{kpouc&pV?tyE z*)IrK!~s}v+FLaHcSJ#ydry$G&593h(Ps5#LIA;z`r*6ujFL$CcLM{KSz-o!6`{Y> zR^>N9?~JPol!{;{Ezmz+O$u#PAhiS3-pEc<0d-WD+evM=GM$rczBu8 z=OrU0!mW=kwg~5@wlbAr>-bhst%T>8wocVr?T?zR4w+_~Gd4XgP_2;5*&CReZVp<; zmj}xYJ^eQIE*!1o%X}MeRXQ6fn%x^I8qbbA10AtiTJF?ztv%zH66e0Q;u=WyJZqkD z>pz!8HV6&)rmFRXVwwqeU9}b4#1jYjQji()u#S9x-V$Nm_LA@UX$@T~?Om(xT`TN8 z)86(L4sEYv+lR;4=p+!U^;Iw7@(;Q?>G(J&Uq0bzdotj*gO8|?|MmINewA1bp#WQ;o$D68l16aRJ#jA>xPCGu6D%f2Db~Mc6joZy9=sz)XU?7q)Sp`l#@wW znJ2hEcz!U_<%0*hBe)y~A7WJ^W<+yzz=3DIHSAWGZh_x@Ks%-Bo_|H{c(#3)-Ck>i zlE5vLkC}2q$H>LuW_eQ2ta*LT7Q!rq^g6vu-7EC5 zU-{SYh~RZ!HLoEj=?G~6eralBE}sFWTF$#hS?5Z?;T!RV>fg^S zZp!rPlYQG7Xfbd3HYB8LOijtdzxjxBZP|wKBIueAk`dr|xp5ruc~Y#*b?ZY-*+%yz z)KK8=(aZeTCh>h2gW*-@j@O!_YYnP4T;X95sraL{__;&r3va;>JU>-j=kta%=dUk) zHxcDA*V2}<)mrG$e#n-wg|op4CEtKv_bfVpbN0-RoCcVjz=t&E&n8`l($}l%p~6_1 z=Jp=#-163ofZWo>bAlD}wqEE1UO?{#DF1rgx&sff8)Mh>)VHOdp|<(U6c1q3MnvL+ zduQI#sadmD;to;RCTDP}v!KDdW(Mve(5HDuiA`@;)^mm7216!wf;ZpfjT;O7y4+0P z+bLRA%UY{vFEWy=_A8oqu|qH01h~`)^dJA=N%2emz$@uW!0QjclSymz@6j9aXs66OqJ&7(80QrXp2&R`{}`O z-F_jUfu^&H$QM|b!n1*4tUA6JjwFGTYO{W4KCLQstKN&{ z|5I|^w6k)-#tZ4gKxFTB3woqk9DX6R)rUL;?OM8GamI4zkQzKUnbm$PObDEjNLx|5 zd*aRLhY50XUkHCE)KT&M6#41B{NO-TgE&{F#V0irjif=5XAiYZy9k9mgLG=?Pd@6+ zfN#3IyWpuec~8|0A}oXJen9S@3;x=)@qkwt@aMzL>8%}!Hg@XGzIT2*B==`wAGAWh zrfmml{_d%OE$~R|R+}<}i^B*tw9|B1fe+FBqgrU{;pCelkv5{QlAlXR6-wpEJ&>90 znNYt%;((aadwY)D$$bt7LEJPUXq>HF>0*85+V-|Z@!Hlgdjk%vSkXp1h@rhwFy93rU<6?v&b)8th%xyd?)Xc)h8eL4P0)B2KWhrn zn4~E8FqC8eE;@FFtLh`M?Z|S&5EY1h5LS{Ckv%d>H*g-aDFH-TpJx)BTf5sOIWnZCa=Pbr&{w@Pdt5N%4qonrvb zH6`;NnmF0Ducu%(%sOQ%i=u1F9m+hR42vRg#YZeb|HsCtdHl^oH zz*Eml#}1*+1lwiTImAob4pFaQ+a=dUo3%#g&L`Z>{B9vPOCsdd{wAgOxU1zaS#7`~ z!=~u-8P|{rlaEra6w=w~yvQ5Ar+RIn_#?aJA0G48o{*;#vqiMG;wHJ?h^KRo1*Ny? zhbCV5O4=d72NP0$|*trL_{5caY2yH z0Rs5D9ww3)vGhoNpn1rt)_%szRL}5=iU7xD6W{y11Q*&%Ih!-On;LRRbK3Gna{4Gh z-u;zY^!Jyn*RNpVTu#OF(=J(;NdELu=wG3@H0-a8H4Lq&i%u%#qYy;~DfZJ+ z(Ez=Wfn4loN6`qCSSvctFqMmtCjd zfYHg$#nzZn#=_miQOU%?#l+U=e^Z=_HDJ7zmzD&cds3&2?<6E7goUBuAxP56f~N(+ z8IXSi68$0i{lP?-gZsy&v(kz4mJgluXf(C^)?(LGtwm{I(O6+=B9_;NuR2w%AfJnE zZIZA3uDlve15dB_#lN06FRh<9oUXd>2U24R$O@?M96#p{f4zOs`yb7y#x0xTXcNc`?vCw8@WVG4tBR!fN*CyY;uoMLzm8klgPSjMHIegy449 z#q%hzJJiyoY83A^&yj@z*cc1JtTU<{agg*G;L?fs(Gkw!vKJ|MuwH#Kvv;TY2#?WA z*S9TvWnEfWr|leT?%#MxN_kEkLOs1a!TRe;otT}|7kBU9x_6a?ykj1JcK2UCLk-Nt zg?%$zJihSo&glBBoJXnz%{*SkIaQDDy8%3TedS7tL5IcIJNhBL!DaROHgWJ6%8cW3 zJWGkqx)@J{^h$XyTx;L}9ohLzxE&-V{v}*8)j%vdi|qBTj(iV=K@Ia@k_L^|CEz+1 zqdFgXIXhc9yUg5g%xem{I{J#f1B9z++q7Dj`LmX%Wx&`LZXG9-rvZ2E_Fha5Io%RB z^eSF(Uycl44x&0z!tv-B*Q&lX?AU6_5mz@!Y46`-w4|k$RU+MM7%v_O6-I;(9tL|? zKpiIUaN{^0EDH;~z3T%Yh|rI--#YpDWz))skX;5#HR$gg>Ljy#AdYy>er$CWS3+v8 zR|KjTYAG~lwsCGMt$}`6XW<|ktV|krhzw4xbhw+0T-B6BRaTaIIk0L^N-*g2+7ULj3I~&eB$M)ZkCRjX-=2{vx?)=<$b<;=Z#&oi^ zfpW4+S5`N8d4BTPKA2Oy6$6I}CA?L~;XjNM?z*i>Cj<)=@|e%Th7)R^Z)x%GwF12# z16o_abp~Ox@wU^?fw!Ki$MPNY@2yue{>qI))FTcSCD=wesX=&rv+Cr_)^)S_{aNjL zrCC+kN!qn?yfvknT#8pTlyQyl*fvOT?(rwsxiG1))t#psLE#^$A-#n}S%87Z3ffuW z3q80uq7zz;3O$l?b3h_3Tf_eP7ZO5-V4#aUCq}St@%&m*OL{JfWGdXppkcH#ep>zy zivK+K01#Z}tB&%-?fuWwVREplZ7mV4#zy>>!F)2owXjWEb8%|T@z}_f(Ngzj4rcA_ z@i2DbV&^d7yyFMswig}&&m7~&6XPgjZtDf1XR z4pHo!u?sx#ep?Q^)L?egSuqhMZ^+PcO{$%9ws3npEjqHh2liaALP`cpT}w2Hqdc4P z(oV{`6H_GC@`_N3@2+8b$;XsoGcA{)h%(uBRlb_2e9aC6aW}VEHj*-7NyhgUlz8iP-S0Qn!SE}UPU1y8=FW!h{Iu5l zkGTZhUOk`KqbAjJggg3kq8_QHM+O@92$29AG!o2xArKxT6D z@|DQ!Rfk)TRI28#Z>y{aXU`ozO(VEIHmFfb6Y%IG!{a@XMPVm01wI?rwL;<08T8$} z_={R`=r0an?90Rf*wg=Z+7fuF46L+F+n-+*m7D$7Au<1RLI2!V$OS4q;vEX9TODOz9h|Rs6a4zMnSvAd>*;`n^Bd$$q1FBXYHo&1< zX%6U|`E?5>IK{&(fqj8_9W(36>+J6lHgYJ|-ITJ3(?BglKf_>q3Rj55k=}$NlJQ-# zuy<5aMZ+MR1Z5H4qM>tB>G5YLPI*LBy&m4BwjwVbVXYd!`c94mNpqYl@!;VYY1}_4 zhB%b1-pPSBl=5}-D*Y|Wkn4)*)<8w%AGav9LSx>&#|7ppkGD&taFiC;9n(&)b=jj~ zH%U{~nO9G-x+LzT5{Q`O?&yP2e2PEBW?MgZFsO-g#QqWO2KOLN+(Cp$~ zihpptSzHP6rvw9Xf|%_;t(e~dSqS(Wn471f`9cYPu2j@^VSoc0==6j_5o#{(qzlIO zd{8c~q@+X@HWH|?gbhg~kJh@>f4GGN7mso9RFF>q&9qa-!Zf@ZR|VHk&`JQyfM~Br z9--o@7d95mVB=soH_reJtT?Td7N4~u{OIE(B9vh|o)s=)8W*AwqoWsz!-%L5BW+Io z>iMmUWw0N7ppYqk>z7H~>$UT_2#=-jO3!}Nye6K@O4*_qAzpQ(LTdE#`j}NA0(-}# z*t$*ApqShGi57dusMvPP{P&u9X>^ZKp_k2r4xZ1f*mmo@5Br));k(TPINs-+7}AP) zYcwIcv*JB+4O=6mjApv4{@FBu)C`gT=9e1mR2AOD6et#i^pu?}A6ee~5-%|cA}Tgh zoAb^NRpF|25s9&WRA!swZ*WQ#c%h}$j~dpY_{mUS$B?`Tl4#{?on#RlZi5Qcod=R= zf&&Wf&IBUfqpx0xD&<<+VT@^um_Dh21b4MP1Hi{qa^=_^$AiY!&yp4;@M94QjCd0g*oGU# zq}8m&W!Xu#@VR(=c-;g!Ffq&5b^~IddXBC60H~j9;;HPZdu3GRhD9B}(^X!#`pkUL6U_)^?wG$!kTp-* zQnD!?Td{@Wzw^^&d;hbzO9vmv%Z-KzT;8eUbP;PH0{$q?6fPNe@ix?LBvsm<(%iRX zmL)r?#n34ZgLP}d1ktZdbCw6|h7Q9#P^ib*{5Tok=q#JL5Z28+IUIhYmn;LeY1R`r zc&$?_z@@Uh44wN7*~aSmFe2qdPmcZQHhIeq-CV?cK3$p0RD)I%8XBJUct)w{PC$%l+rx z79Rzl69Eg|rwnJ^D`BOt?1JQEFD z;+X(j5wKN$i)yiKPdLL5^CJkVTLViG)G8+ce{nTgJ8+qdJJJrg!kyT9ChpY2pK*mO zUqTP$evy22-iQf&CE?!q?A5TXmAdC8#Sq)b1&H)?;AX{M~4YYOB&R1fh#RU%iU z5tL~JJ}m~TpJ!|>_z%*Ji-7u=zX+QYb5?ZK-gmYkT+y65WLh1#t8DHp?rb$?AXDx6 zeT(%0XRwfj!koc*6l0J{MH%9gJ?>Y;0bO`? zvqb*-#N8tUtC)9_L}^Fv0bB&j2v_(X&j`z(Xz}C72!c!(#$iN+J>&H$XZm4o+&0$j zIcMl$1B5MHPnPZ3JBRQ*ggujvjDvTCXVxpzj)DVd1YvXUUnbncAqajrfg=qW2PWat zI8UrsR*@_ZtOUm1sRyj#f#Ku0NUQ>rcLKxpxPjAm{NchZA5jOK(6N6RhEc=Ma16a7 z{r|**L%p*P3lkKH5O_w4xd2I{M;H;>SwHH=smUwHpnXur94)`1fetLFC1omPZ%^uHsWpd5`TAhfge4BVlIW8(&% zq8w#O^FF7?-13e~Gh-thLyMPgae1^g7XA4<=?LdP92qZ6j)RC7V!{Zx zU~A4n-dS5~F>e#1K?$xSMDni@nN-`fA&5d=G$ZvtI%f_0%Uj$1YNCTU zcdmW4{THYsI^jb5j6RkqpX6XszFlqJN39Tua}0<(?-%^#4|3oVTyM(}3RwJarZ$;{ z%)JW^48pxw)*6j^#`lOjnx130y^r-~_T{wtan^RJ*KRV%zGNDeT>P1ZJ0=(h5VbkP z#F|WK-9YYmqVNs&v+7W#V@tYK6vQj4k&0z8tcP&19Z=+sMPU=^F^LQrWLj-YTqTQJ zlj2^#cJnX*-8AygB=mc7S__~54f$C^KTa#`mkg&-E%GQLrFTXzSc=bo) z+V}-p7TEKvdx*U0Rw3U2SU*ku{rYjh&oj=>B#(VGP&?Vk=C4x>BHkoXv$5MWHSALC zkS1VAk!kFi7v9a)0>Y~)$jL4ZG_=X`bFz5W@&&w@(?8Yu z;Ln>fnFb4wpi;{i^-KiCo?1!O&Pe012OZ>GVk?S`jK`SI5#BIQKe3Ji6=Kf0dE7sc z;-CDKU)c?0g1~C~7Z9B{azGZFlJ%okoQ4~AZ5H(zv?14&c#9j;d#Z#<*txptjVV*; z9ymyZQ)Eqo#J2fXw1=D2yK|dud2k{X7(tRR_??3Xh-# zhEjfD)D{dr_KT?V#iOT4S;6ON!ti@UGvvk+xK~aT+Ko7Osl@{Fihlg54JAS^2H~>C z8xH;Ki@o=7=B1)LCqF0(is>98#Y>z%jfpb-T_Mg3?eJHkS@%hPWZrZJ-9I(hUfbJV zM4>lz&iy*&o>$wjc9z0o-)Twv$?_{)n7ex)4(w$5Dczu?{Vq0Qzx`-Zl9OcmWI1y$ zo|^xoGB0{W7LQpoNv?y{mQz8BSEHuTW(XX4gmfK4!nq?Q^I5(K8t>}&0_N)T;iXzf z#M=u2@g39zN>aHKA=XZzk#g3n7u!QWER{gi2Hbj^x?;$|v(!Zs!;1~=gJB87Y%vdRW(*4&KV&R*h* z9F-&oR49O2rc#t4J8DUXt!EwF5nDBi$sw*EhO2d`>B^Z$R#)zGC}oSrJI%2fA;ci?7p8WKJ zE=(TR`XG`Z&)z~q%AB=@HgenpJDEPJ95A{BJk+7XkhYHX&L4}$wkdj{RX}4_Nqga3 ziQfCYl*h4ns2`5!HfOxpZFbDosqz8)Vj@2w1VKjRJ19cxp{Rcv{OoMWCd_>N;VzOt z--+OM4R>4PXQyuTd2d%*4D8Z)61C4Tg_LavwO4f)m!_h}e$+b4Q7qEa)* zn$5IZvNWQ{Vbl|+;R9WL&%|~4-XiNSf-ZbuN(jgfNHOY+!%q9@5~*YPc@9~7h6i%1_~JmYx2&^1|;G9wx*)Jkf}v9|N` z=P7a|Q5e^?N^ZXc0;GBki-<{!C5U^CEU$}Dw|AMPTD0PBQsPiIblkkLc`w*)kuuCH z%r(Rimtm&j{@aXW#idfkGM^yro+Owy{;m5XiJxF_c##gzliqul7@xyTvn^lA{-aTJn~*i~1?$ddB1OMAeTY6M|Zmg#%xvj(q7bSIib8@nQ>Vg=7PD^0g)RbI3Kj zh?377*6Y1(bmVE-ceC>rW@5l+SR%LyX71Qj4B(T>I*7HUA7ax8sZTnhtk*|X2x~2H z3bwVMNZ%>^sa@nYgF_tbxFF)YDy^t-FsC8ZP*gR!x zfv14gpCN_+!0cJI?t@9-?S0=%xaEWN;|lDqgU|AynE^gyTuE+094Y4sl%M%IHae>{ zf-fqB0QGJix~(+Ygly@L5L#C(7IViCNAemLZ-A<%x z?He3Uk1WzF^3|?cB>Hy=-7Bp^N`d%{oT=%2!w3@&&65l**-=nB6^-5zZU$1=P>DL+ zum~wM#y!hXYv$tL#{X*EP17~;q|A3ju9uT-DFGbO1b9UNO@&fjM4CP_EIkalWd(Hh z!zJ~f3uR#Kk(zev*7R#2WV!3Y)Fh9Am{BsjV|f;fU~=MgT<8Lnv{{-~x;Q)Na={im8^m4MVT}knY7B1DS3Jg`Vw7;N-VQc$CBX8 zBRI*>TMoDE>Y0|2jl+FM#(dO!)`*tU)WSUQY36eOTEf4(!Sz|{r@D4LOn6?da<^sn zedH$r)Lr?d#pJGIyBLM5%1NhGD{dpNTcl$Btk1lsWlyI?NPXFsjodxLB2G)@!qoRFeYuD2}uFg#Lc& z1$jJZSB|wp`fmRb!c74%oNisJU#_e$P=BM5yVyP2Le{?j9%~#VUc<^Idtf|L@l+-_V_<1 zuQ}wqL!|7ylH|5TJvMhZq~Edf%67#60t3t1QgmDVY{H%lE`Wg|Le~*3Dt^D}Bzy=> z$2H@eB>Us>!ge9GxTeOwBUJEIMyfZJFpgvDB-Y`w(gg{h0_rlvZZy zo~WPt5vFdl2|(5ITzoSQkv0h#Hx3D0--lh_N3p-7u)o8wzvHk6BI66d;}aBeUHj

cIPINwn?k8PW#V)X~Wiku{NZYDz2dl1lE+=RB@bSNc0NZ@QFXTm96jw=2uN}w`emQz+Z zBi>_Jq0-A}CZLDZqN`OE%P~YMq!Ou+$$YnRDdTMa89=ZZ8!_5n%_AjYu1I%#o?7cR z_MO7BmEDTo{KD!Q_o%s<{js?09Gp?+vLIcyz)#yAPVZ@dUoro!Vj%aXotaG`L#4(= zkp)k9ItciCP7wg6u!eum@rl5vCY&-S_G$mXxTa^_x(nC2Wz$1>-RjJe?#VmiOaA^u z=#JMP({q`XrIMP|dhJLjew|0w=ileiimheM+_!t_IlmdOl4f=Tt5bfQfloZIgXd2GyH_1@f2QqnK;orM5#Ejxd=+*O=Eq4j+CQ zY~Ul~))!P`UYv!_?jrl_8b1hO89S_w={Ug`;CeI6B1}5N9Gs)Se*}e`6p8zIR!z6; z`dvDFnwtAX)he~EZo#u(Ho-L$wXgBFblKO=RnWOuhv}gDc;=~C?@QzxW%h>9r7|E< zshggGNlCO}SYqSZT{Iz2a84sntaQf<8=1yToUUfSQ0a$F3!IC3+eErPFsfG=!Ik&N zQh_p-Fh1dGW;t8r`?2iY2pxbjQpVUIZ=vQNv+BB#>oN4;~YPp-|XpcGG#*TVC| z^~0~ekf+OM<^DeQ)7Me$mA|I>LGYV1?LxcD7tcT4zL_-qu=zXQl>2ESC|9|)5bIZn z+U-!!HIxD(yPAEJkj15FRC!w^(oWRH)lBR4%eJn=b%lqjZpNRYj4j?P)PusO9?8{< z`+q#?!->Doo^Rp1)bsg&uJ!aZ4%r zkXQ{L>4b9%O4xbHElATc#yFIbVct~!`^G9-;J=$hCi(PJFAG9=qklQB1TnXRGM?eF zK@pjOM1tyFWwG3@I092-_sbAe7RT}?_f9Olijq7zsTQE)nM4kuVJi183&QO4kE1R+ zqhCp#{!?@XZBw5x_4-0%x5P9BrLr1AwkrjV7oRX``b32(>aQIdL335oFCKU&CfR_g zGt`p~|6r=h`*F=rfQGQ~!mZjZlcz4lg>g32Vez|`pwjQKJgfp^v5QMUwsTu%7Kcgf zqh;PX-WD~rK&Isw8Y_8NSZt%W@J?tOy8H{vPN-$0DGOzZH_dD)Ei1NRAsaoLasPzV z_#cNs@Gy$FEwdq;l^y^_zzRk(ZrtF#=+U1+1uH~-J03e z!pg#%S=q$Z&B9g6$yHlHUhUrs2b2GWp1juf^H$SnGva4-z*A)mE9$|JR;Ed)I#QP$ z{T-}ZP68)103FJeYHh=3Q}wvRC8H;&Hurt4SG^EXfU#}DoSFB6o% zpU<|L72!6v9kPgP6(-?$>D32;oHfeB;I`h%i1aHBvWUo)2PT{XMd4b@iRIz`oEQ~@ zLnLIf=!Aj+d?5`w%_)#UnW8T>#~U)P#sAsi#Ac<Cy44 z`^l@lFXOrI`m^e}Isuc2o>x=0|K4n69Epfo*ipFe^2PA&x_A%lO=IEq@o1L#I7su_N5BPBg0jQToYwXdk}ux~b?G~lVPOGwGc&Pdwm%qd4``_TmQvq}jw zf77?>ZFx{Fue?j~G511n#gFgXeb9#f^QwHOyth1nSR?3IIK|6yE31mV)vAZMJ?QG2 z2UxIXwCi-cx7**mw{m;q1bTk*IxO@TFdr1>5|H?Gtf3vwuGMY^w|g?46|HeGRv_oQ zUoa7kKqM>$tp_9V^R*QZhaqwE^?>eRZN;Uj`MMw>fi{v>sC+RCow#`xplwELV!r3% zX0$x$g>w-r^e#Av4WLGF9V4iz*p9zTALLp*9m7Q2mA$YMUB~6_0jNed*a`lrt@o={ zQd=*!HK(x+!H&MclX#T~<#=Ed%l)i)E7%dgi@Ug69*E`cB>C*=-VMB#YRBFYa{nj9 z+{pvojPc>GVw&t!Yq#TGPBqlYCW2q$H3|`5GHN>wzp2*j1wZ~P)Qf*)s_`=G#l8&C z-iv*7(B6xB+*vms3@5Xv84I6XR}M$)ZZP5Ix*87uTvv`lTv^{A2$y=m0E`C{;rq%0 z)#8cxecKAHsD0^x&DetP5QHL1l)gwsXDGhSNzH#-Pk$j$|$h%cxIKj5uo5cOL(a4*EbXWe47PE2%k zT_fDU^Qvm#MsQuB2d>+$v0FpA8=xo(c11gsuhm$N<(JK3JSsv8#bq`S|2q$7ow4F4 zI3f<9`x*yCkqlQw5u?zwPAQjTK8vnSqQjWHLZuT!aakSACAX?Nsmr9(F0<+=3Z~d5 zAX*wc1-SuvooLTbsTT&2oz!I9K!U46J;qR{uMlnWp~z8c;*@LCR1AZL=!95-z5g#P zPSvDaeVr8Z4bnqS5>izqkE9-;s7UCO{LMLOMCsunyW^w!tXS!nu-t2OQJ3s%ud39z ztVZ>?UYft7suTuTgSf_4r+s|*@3#OgkPLFOM5G-9#`%IV(YIFBsc>13?s2np_geLq zsq&e#vR(F~Hp4eS)hT9qi|Wy{pmlv-_bIPc-K~iaqH|vNa!yw;7w+#I78b(s1_UN- z714wp!Gs;&gdO3;JTc8WtFkhya)TGZ_*-P~_wV2fu$Z8MML}a&1Z@ZK)i)*ndm-dy z|AX20qu(J2L_G&whUSv2Y15R<4sk^YoOCDRir&18Cnd=#qDa=f{%He;q@yAM(zfBG zOU8!(ZLR-9$`o)|z}xv-ypv*0Oedh}pX9dyc_l9TuJDzW%&s83Zs+;5_P`J2l}V)N z?^M|b_%!z(uaGL&y28Qk(So>!6>6l`e{lp3%j|+NRa`tf|GRX8%`>e=9#UjmWQCej`mdP88J(NEsZ)&RTfq8idIWY zDIGY>OD1=qJ+jVyyK1MW7Oj$*$UKVYS|;SVeW(?zsO&iuQr{lHdEe}t2Qw#)U8-Ev z#5@%(gBMcs0n>{M*P;5ov>F9@#>z8_EcR1V|4qy;W^q$xKAgX8LzrwVZmS2R3ry}{ z3(5d5S+%`c(WBN3E&O$r(ziWS(4CobhWFcIvP6A+IZR+pu&^xHBM)4|15&O{bb)05-8JN>8=Mw`I%!Oh&=`toOOP=wI~bP%3^SG0C}}$*)+aT_^f(4nfrxK^%~DpWj=lW(FLsTabIxLLdW3zSp@V<9;=F?k9`CpX+0c=sjC9#q;; zLy&JkMY%5b3Q?CnG~u&F{w%yL6uWAYMgGEp4YrB&8)2zni&|N5#Y)SbI)69_NQRznU zH|Q+cvi21vXVHw%O4-k}Q5?Up{X5tBO` zCZp?3L}f=xRAzhN*OfXTv*p|-kKI{K2)nSAL2mEp2I}1If(R*x>*|OOyp>=s zB!@Bt5h8}00c(rv>ZA_$KhdM>6a8G^?1swR?&*~vVh@~?EdT<^&X|4K?Q}_mwxAwk zcmK)&G1a9JA#ix(-Q8Zc$cc$BbwSlC$0O@B!ULE0|o9=0%84fp{g{Dow+&50zdV8@LSbLvVYQf2ZlHHIE(z4mEzkx zlmKZ>tzanlvR(7W!n$wG;pZq*LSfY&w1^#kO$F2ED9(sR2MUJ4oA#Lu5TaI_@|Ne z52JGr;+tQ@KI-8rxv27yHNY+ykRt^iKn9b|i3jQ;0bMMJsD%KO;-R`oVY*B>l*Lip zudM;NqCn2#-*_Pao8sTu!GKF?@LAa4Q<2i2d6mcV7|2Jwgm)*MD!Foiv*3XZ@%;_5 zaj+uD%wqn1;kmqh%Ab7%9KU_6gTsnwe#jrAyANPbpU&BY1Ql$7eUIfFWKpSB{-6Qi zG62eWizh`Dow^Xk}52=B%*E*?fD zG{xlo?K!+Gz45%ty@%Zp=}KXzCNf=he>19gUx`9xNi_>?g#f}AM7$tH*`>j2VSj$W2>gC1K9`W+*RF3%>(I_oe6I;}IYo5q~cImYvMFv}yIh ziqAtAPTTE3q|o@}b=rYd*{7x<95|*9|BNeXOf_>Fk7yr^*fuipYB~|{c^Z;uJJ6m< z(O}{HP~>e6>i0L)X0QVf+kjt#IY!rTw$L9j#1U>4 zYxUlx@EzFj300-M?~Y0`iMr@uWzDAJ!Nb;p9^(7KXdvU>E3$^R&(8_`Ba*HKw1Dm- zpMH~0<6=9cg!)W=_nB=13&r21iw1`mT(<_*Ta~@jyK&PcSDE8IYu2B2q8u&gI(K;v% zUj!owYHqS>Z3?2>$bDI_7@Rp~idVa}IO>4DjgE=+sfpHNo^{4Zx`8dk$1lmDcHcDF znTVLF4xN6*zt<9_ZlRG>tVv=WL@NDw^bKJ>i z@E4POmV#$ev)lf38#DM{ymCM3y&5BTWDdlA z=IV-yDF>j{50sRb3rc8ii}AimzKElH%3TRL=< z;Og+grEU|q=dpLZ*I-6K%XjFvwpTQv5aB_i{rNjie@Wa2PWX`irs;irF0eiewF7=E z`%`pj^_nX#r7hU{9ij7E1)UcdCwhM!#dm`$+u>@jAZ zTuFqcS`A2^lzs^cmIV#8SL~jc@5u(Ws*QH)S`Bs@?CbIx0^N#f19e)IjXG7*>-H;4 z_VH6Ar&X>S0xK~Lq83)%6?E&fRkG<7u1$LCLY~!Cc{^nnmhIKM#$FZi4PoTb|M4&s z3v>b*Ll}{&gdhG;tr261)}2b8&eD?Q6zZlD^5)5VbjAfI;C2Z*aa~6z)r1mjOZVc zY8ebtii$e_^gP73lN1C<+t<;F|LaB$;X%E>mL=-C41~;`n z+z=uNSKr8a$LE);{P3HhJH;lgIqJVooH(9%eXLw%FPfWR-LA0(&KM@>JF~3qG5TwQ z(!BDPziCfQr2+Cb#9EuwsZTPcvn6)Wci?eXLF@(^vX#frM(Udx-uT6x!?0T866ggO z=#u_DDO*COaye{0o%+(SQ1*oK=6sn$t05VsV)$|)j%5+tS-9~HR{Ipwgs9wZuhczg z+zR)7qh+uAA;otJ*r^L${yPZ72#<wyjxBph+@(`ZzOyEP+G7kvM*(vHGEE`vwIsj%b7h&aTiLz%6tc3b;PoviSt zSjy92jpD!7MF&#hA`A8Y98QKQMwWF)H`tGmcKs4c``wFxdW9Kj;=_`KIiNA~lRXpj zlpTM2E_$Cy6G3jH+F6*s5XczjQG>wM;5uQdw@=xIdB4F$W4dL6y#=OWPtDP=0~~o0 z;jjM1y>7@e_OuXu9g(mOCZY*j&`9?Zt)XVY{~u;$IMOXM@Kr&`^CNgsC|3)K@9+2P zTtnDWSszl$;hDT$Nd2iiypc3wZxg5d$;VgoL4uFVonLu*oCGpB3>S z{27d}$UYmK(Vqs?bFa+5dVd^TNiO=se;7qvek1))h;4$I+4damAH?ZDf|mbxQks8} zssGE~^8Y8qrtV~C;rPE%wG}^Df3?x4Z-!^CC61PZ@E?VebO13qT7agfjhr5xL$m?# z@Mj^S1BHTZ_SC&U9+*l&QwKbgq`tH0Mv1z}Q9OS|MMwJ}W=2o5qZSWP!F{G#=#xbR+A(nYR2{eR{ zM&cR-FU`bda1ZH(UT_cn#Cxy=#H(^*0_3Yiq7vk*d?GW%t8AhZnmSB2Nrfa(D3}T^o1#Jk*cWUYya{nl zU10$H3P!?kPXtT`m%;eP0>mK{Fx@Kv!w`aoKn~F*NJ2{Ma6mESEQWgx;0Quc1(*Uk zi}rvaIu6e#3XBR?M00Nfs6h&90PRKHAZ9V$y8y4jSTKCzKrvAz^p^jCrlNVl=`eh9 zz$HjV%ocf|tEd&cE-h#yIG-HU89ayCqO05vXbpaYti^V(1$e=|B?7u3cgR6%!EJDO zs1FSQ;^20gfU`#6s8VP?f-t5p--kS?lXJc{AaMGxo)Jtxj<3zMoZfQ;a;PCZ@%GzT zHuq{Dt`LSV^@k&sRD~!x;D5L=3>EAoTwVYNS$Jh7s{un17qtA11aVXl0+x%obCSj<5Ux$M{X-&VJGnz{yk}kvu|B-*0PT zo4=7$aJ(Q(qm}Fq6eF#Bm zp`o)|bG3No`jE(EsHu~y_A~x6$8U3HSwF}?5XY)-{1TR_(0t(bqKHZEH9*d!b;}6d zd1X!PFKp0quI{R~QGL#4X3?@n9M{EWax7!`imE~1#j;LZUzKsOJ3r-TW9!hF&c2J5 zSC+5!JYv@~9*cR?cZwp4O4)W%a#OUbv#7pf6gzh}L9NkYu3^O%J865#kdC1t8~ePD z6zp`ZnD&px6unEN7woQ`iFcHbir9WfA$trXg$wtDYPx;cDFBTpB-T*&yGPrcvk$gJ zwOOdS!o|-Z0DF8*XP^(I$0k)gfWM^faqk*+LwRUj%r+9JKdV|SoD8l41}MA%|#33g;X$&A&NxR4qUrI z56~G9I50ZDUv7(Lai43{N|@ zb(H~d*K(7rVL~&k#-#%Px&Dr-3`_JOi7AE3Snj4S6GOldV^%W}_tbGTDKxwfUW-NR zP)>Ixb?<57*ik!zol*r;WR#QqlDzrFFbn7H?_2F2&5*XzUA5h$u40t%c?gF;cLxnz z4MViTI~tvJe?8?6jz|V5H?J=>j*Ft$`q$ZNM`oVGbz#ZASFqb%Iy1oiMln)XWn=e~ zGUUo7v}8Ctp8c^TdWY3HaEO%E&##}GZD4RM%HJtMYPZHEz}2NSIJau9F}BY1Dz#*` zeyIbx+2*a8h*6H%mxdwZ7~+dL@TO(TGR!o})Y4VfRb_==HMqL(N*@5lFj zkJ{5ZdY)oTe&|M@Q12Nzsdg)DB~5trIK>=5W#wlcyU?{aSbh`Fd)>GksRFS!v>9jc6C`Gx@tYDy-oz5}b$WqNK-I)7H!(zqxvu+y} zOVRx;q!Mu-D~JSC7hWAcx}nXzM;PfS-&#_Jb#2G=N9+`-%Ul&-U$uU#!K}O_y{r0z zQz#){d^!1-6|AF}b28kS5mCjgWnrAv!ErN-#=P#YebE6yq~Afm=|PIM)`@XE&5g#b zEejDTsuOQ67AK`)b3Z{UP@H9K3H4ap30Gne+U2!1b0?2eE9GD?Cs#?u|H z?d^?asPL^!or4%OzdXxak@ZJdn55E%Nl);h;&v(JcG$P;E|4zI>w160>gDM=9;cx{ z+`tR;UHH$NdU@cMm>Vf52}WEB+HDV-t{swfm_gcoD{v>=f_7$_wm^UNv45eR!dacQ zs@6JBh4Azh2?W_=ZAZ2qzzZ#YZXNdTP4%17O7Z_UDmtW>d!&%f4TeEIaq(xBZGQryc`&8!Gt_cFW2j!EJ#|^>h2ANAN!w#DqVM zCmY8BDhb5I42@?*+DPkYK3frC)Shgkb>U&YVH}Zx*ozrf>k#F)&Q>jgM0CZn#j4(! z!C|(s_(JQWy&Vut0y1n&_}h7d>Blyi2bGoAFm##QA-CYHn84CygCmZOA~~o({0v#x9Bc7FvLooQH1)u1#+|bpz&E(g!&Oa7r%2t^A zUBUT$Ub}~MUcG-z*FPD;;XR(Ka>pRyo$Ujp1oqghI`C{m-uMoC!XGv2zlMsZVi+N? z^q1|nRZ<=vUjv*=XQKq8H7fLCwQPcxZESnLs!CQbnPq$WP#MucwzLcTNS4FB1Q3UX z>!?_WQXLaN$08Q@b)vhQ!DcpCZ9=|I)7F7J!Z?wWD6pda{uaX(;s5AA;tA0nroA|N zMG-Gd9Mj!zG66w98N{us?ljF)t3O3)qb=I|n$bP##v;RD$T^lA+%dz44M82fw$_e>3X%w!~0sOFFH0XLb1lkMb~Cr zHh5O!uCX#Aefce9hEaH~f?`1EYQN3KWr%#~L<9bEUNBFm!wZ0E<aKLzXk7MI>)slzybV;XGFI!h*V^thR(YINt)}nPCe-bR?laScDno=1pkhE^ zk|GU;lAs)-&cMLJV1)+4c!qw#fTKW>@t{DFB1rK-nZStA4K>1uqlS|rNb?{S<{r7C zu%V8Tr2e4ME;l~^k&g5MbqAMjIJhXv&KN@&@Ny6rJT*4@lt4b66L}QThIkVq#ku zrRo}y$jAm6$v=pp@7Nb1XM#egx<)11vq4lX#dB!2 zhn9MkIHC(^5t6SuUAkS1pT!3ik1Q_tk=uLo0HY`>6FcO;UM-prET_5OqC;6bOC8@a zzpyd*yL6zKAP#PP!r2_wNlY^9^fCrlrBOr=oZBAbYiC&ZHqbV-54D zzpE{BSJYfz3QFgX{HA>R>x;}Kkf%Yk!y^ABT)2!%&#LqLo!oqP=iz_#PvP}$Jml@+ z4Bj94Ie!J=AoYKJgIacRnNEu@DBuG!{T&K#$^Cyd#LP7V&oY<93Z%Wk4kSY}PzV1{a;8jABo-ti6L!#I%wID~qk>`Hng4~iRL=|(gp&NeACi@^^)9_zvz zL^Kc`ScdAMI7)UPnm`Ys|1bydoD(Y`UDE?K!+0g)Gb~Fxwg9^)6xO@u#D-yVCVb(0 zp$|-5*}1k&3pHS#lk^zfG5L4I>czDOWEmfzG?5InA&2bY^XBIofR>@?AkHfWc!S}&Mj#6RnpnNm`oyU&NQm&%#0q=p-$es)V5n(P$XSCo=un8i{u918 za>W!oA*{~$Ob66D<5ZZ?Y=%>SyuV4~`ftVp)2IvFS&~X$ZX>T}D#XjT%-camWd%EuN8AS`d8GAgxv6-d~c+2$Z zRni*ibsNj24NdlGvxICNAtnc?Zp1=ZlaMUTCcW7!4lJ(btSigVEmSVDE3RfTdq%NF zGEeFu!%%9JdlveVg4oZ& zF%d;Gn0dBN971nCeZMG2R6*d`mmZuP6HfJ#%C$j5jBEYMPv=h|5xh@U@NQZCsL4uw z(0)|zuaraEucl(EgWF-DO+S+rppwtdH$v}I?xs;cGhyUKU(O>0AUJftQSY3_?u@{{ z?Ge6NasQi-JP`i<6u|mX4iusc|3-L8V*NnfSBgFmhJN)jePRB!hcg@m`zbwgX9On9 z815AD^*nmFC*tRi@XdHPZ1!^di+lWT4?IX8;Tt^mSpngjyahS(;2Y_Be)R79*AqLx zk0}*IGhblPFa^{3U#Ng_Kt0|AKy;4N`f(kN#gl zJmKGvFTMZj`37#|4}!o0m8j>PzobpQ;V$n%mqlld$wfS>$zYwk#$R2V3 z+cJHtr?eEX{bNU)KOmwMoS(tY{D1?mPo?rlj!yQA_mU5Wy=Y>WZeYhBZhKR5hJu6V=k z!$0H|{@YXB_>i^0_HxJWC6?e00c8tgY51Y+{4CNFePGkHj4b=l0#>E1IV7+5$ov`! z1dAPCti<+@zbvNJtDppzY6G%J>#MN>B}g^Vn4TFz}uRPyg6FCV?hkL!7_44 zN!TRV;c8o|@j&qb0V52H&TM8iEZtMn*5IrZ zcm9u!=z_=r)%f72-2=XCM_;_)Rg0{{98;ithu(KWWH9zo=GO=EJMr%?rQ}P~wQd!bn_u5hTW;Q0ig#m-^ zg7&k&+>AT?yWW2bKlQqYY5@F}edpZ+nIjuUX(@;$ zA8P*bd+_bbI4gkgFA=!ZV4#alPgcy08^8IUkJMh$nB`^86Uvs8!mp73E}Nq&bj7?H zF-%9f3Je0Rh6%9xkD6wNkH0hyS0GY>2?r zxH%kd>F4;HWSwRyRFp-$XQ4F{;fGQ<{2z>C{WR#$FR-0%I@e2#rPMt9}ULY_~PyRA7lBJJWD|?{DN^HF#<{+^LM4HzF|+DkE)kWS47p7 z{CSYAT3Bfz?#(U(;-9nbFNnTekFGb3oxlvM@qGZc|ANb(r+aK}Dd~ZQ^A>3NI% z`NzJAjR@KO^X9*ZUoWk$7kp^#Q6u%r12GOE{NiA5e;LxOMmLV!E72FPTvTWY7FJ{MdYayM1s6_$ywrCO|)prF)xMHT|+hD zuFVLcX*A0nJejlFvx;)>ZiumbYBjLFkV?)kYHJ9pB_F;z%*M(m*h2UKC>n0$nkEaX zbkByX?>)4PuOC}JDX_m|F`b^X47ba+G#a%uhUuC6iTP$5#u{v#Vt~@NX5Pcfa47lp zJQq?kk8)d5PQaLPSG1=LYO4bp*`~LL`t+q4p1k=}`=+9m zB2)!zG4?bLyepmMo7Fo)w7RYvN2PbnIO2^fb|C%i(XSf)CTx9~I}uI4J!zbR6MHg* zVEpXhHx#{+^IEc6fCk63n?L=oOzx?66n4w}CZOG!qeMQ-A5`uI0|@43s4KFBWE>u7 z`9g&hq=8l-?>B+jQf8n)R{<7K%Z4=|P7@f?lHI4+`+$g7;;V}c{JhORe+$WdDi4 zBZvP7nLuX0SzngGPG*U0DobMd%*raD<{YLmH?y%i){ix^{%jFTVN2K$qT!cQW53@H zn-_1>jp_SjZp4Gyyn!SdE`ua^Y^-%Ne`nqFs6@IhF|`MY^ez%9gE#&zuMC3BLfCK; z$HtIBY%CeiP9#O_Byt`*8FI}v>@4HZxeFExJ?Y4pL&p?zHF)00HBWVW-s#34=q#iB z(Via1`54R8spk&6swa-oX?9iL3S7;2h9qWy&Y$n%d>t4DN=|?yS;`6(^2h%4jGgTB zbi1;X9od#np&Z3DtWUw}=)U6Wwm;r@&>_~^OOAfRoQDQt(@2QrkYy~FT*{`Ct63ho zh2@j`SpnI~W{_s6+skH>$5|2C$BN0b@CyDCJD0r7T;w%2pB!Xv@)lb_-e)!B19+YL zjCsis<|p4X{J%z=Pt7bq6EI1`4q9(SSeEh4;F#ET&OZ7&|n_vjR^asFRze1s7l) zw~G&-sGrl@*+-9Vq!gSy(PWRamTx2@;k2#I5}E}LKsh}HMN-^dovlwv&?q0BOcP^Ml#faOXI25)Fxte zs0P=}PdH%X)lEtvzSU8p5vEm|Ya5yzv()e;?X z#;UMrtV&C2;qlsq2#EmFgbq`N0v@11Rf7toILL_|s$nrY$ccuia0qOt4v}GvkBW?Q zh^Pn@qax!B5wP!e_8!W_XIO_uWhSDMx(pPfG7}9Mv%`#IQis_(*E-iwZ?eN26@g+@ zWU?WW;7H)si;b>h9SPB{P>gmRYjm|ZEE(2eQgMqFTww@hIV?s`lmku*Wf?+!9DS@~ ztP?O2`dG8A`Fg=+U0_I0arB9i22M&(LA#%!^dxP7q~o;#(ytS= zxspE_g(e}Kf^ZVTc!UWE%e50_uVvaeNmpoNCB0NTNz%)-lO?@e%aZg8?G#D>rj3{M zN^OFqS7{R^U8PNubhS2F(ly!?Ntfef%Mex|T#9fR!sQ58Ap8yCN`$KrRw1lLSOYNX z$xXyOF>%zBj>K^CI${MQ!TllwuAQ4mAFz?Z6pz{pCq38lMP9_#}EdKb2PSY4l8ilAUC2Z1Qho_r&6l%vd_vVB`v~Y){3`Y%U(G4^!VUWxp2V-^1Ne13gCEus4;m;~J@#mD=`SZ#a{(`cbzo>-y z%gR2!U)j$OC~xvtln?l;$|w9arJWyCzUQwiKk+w|U-_FV=Z93m-%|VVcT_unR~^jX zQ;*~Cs~P+Q^<>_vPT_589*?NS{IFWVKUB}*AE|Esv0BGJQ5*TE>QerhdNDtuuHc`m zSMV>?)%;8KTK<)K6K_{<(=#;s-w zL@M5!9cjreJi(a)Q4QBV3%og*;oXZmGde$UY1onfLgM(Z#Lj;s1Avu(fM5(Y)%f%} zXgm9CAL+A)6~l>}zJq-Rd|Ddjj%{7XQsUud)J&{O0&yr7BL9w|$NFqYfh#CL7!H`}8ZTLwg4>oAJ+>=wBusx09LEnpUKjSene6Sb`o!d`gP8RvxbXwY6|JPMw;b zl7crB`~>s~^e7$`m87Hk_MO+a?{CmLN~0PB+QQ?U8DK1uv7H?R0{le9p`>fmP^5(? zL7hVxC9#F~nW$tSO~O7q*z35;4viP!*JEWI82LnER!$~~N){QYoI-{w*Qb?97r;%%vndBy=m^3LR zWRr3_*{qa;)o$$grK`+v{R!LItI}$(Zq=2Awa(Ov;8;n3=uDcKwu8Njja}cplsT~2 zvtY63kbcU!WQa1C3{&QTyB5vU;<@I*Pp<&Q2?Dy^nn?B^Zqp!M7q4kmcRI!)3y zt-~cfWX+cJed|(5Kd{b`wADIa(!p%*T88z*el!FE5?Z{ z`N(z_F?cLxTjv<1=nWwPr}5Hg@Au%&VU4%y7q<7TW~WI(59sV z_S0N|cC7}mzcwFmfHn_spymd2Xmx;twDSOyH7{U_<^vq8RRbQQRRIpsrU9mEMS#a@ z6@WvvivWjd0|19>a{-UjJY_rB$JTRnC2go=9x9oON=`>5N28K{spMd((UqfN9z> zfazL3;3(}Zzzj_T9IZJ4GquwJ$7n+U$7<&Qj?;z%o~ZQ)JV~3CfQgfkjXZNGf%NGu&#Bgjg13xs*PbhOtUf6F5AY&0Lig2 z43AtJL$gn}F^rHr8$;9P+ZcMGz{Vy5nPFoSfE3yoy5lr$0pLsvN1H%*)}#FqUs~7w%1(AZH$*xDmJE+RH`sSX@b^$;M12mBPkE z351r7#RGvlGZ3gu01|Iw79eID>jNag#u9;8Y%B>#9~-j*Nwl%PK$2`s17fu?8<4&> z)~{p-J3!%>h39p6UhO=vZXm}2=0m;<5-YiY3?z$4Dp^cMzzI2qEG6T~U&u6aA(=t` zN@kIZ$eH9~GLKwBykt2EkQLB!DOpBVlB>vNFxKU8b-jYzNB%~(k}KiW{VMVlSw&tZ ztH~j8Lb1!OPc41>LV{dJ6FF|y4axP6ovB{^z0R0UJ)+NFYlFJ zTvCx9Dk>^j7S2yEC@D)HURYcaE-MJ-h0C)U@ZNnUNSr8fro=f6SQb_rEDINfmScSw zs3M-vJ88tYX;}Q2qeqOI zj!haJw7{jr%Cgg{Uv8%=e zY@9t|NofgnTudm=4=*QCa3>TOhs$L9!&oB2fp#W^7KDqWptPD)SRBr+B<4;h*}3d*9f>LWvCVQRBIHC$0yhCH{Y zg^R*@*cjh5WPJz|v3UQqlFG8Y@Yq7C%QvmE6x)X4Nz}^;UlBsq{*g-iA>DM9XVx2?r3bn3?(r3%ut!)%&bM&7d;+0bL@F}nj!Pbsdt!V zUd5t9Oz0F^P+n0M%B#rELrKYA5Y8@CLQ@#3pq_U0-eUt6DI${d>;)zHRAVTQ1Vaa? z1|G`CYyuG*L!u++`IzF$B^YTTBjdt2_=S0190?0U#d*XljLW%$?|Oua(;Jw>c__po zR5hV=9Qh)ws$U{HIV1DK$bqsDi7<7lxTH8+vLw53Nof)GxuPJuxFkDNwh(LX-q`CC zDnmutsIRh%!^^0T#Vd|1kyATVT8fH|I$yRB2VNE9;DhQer+(=%$8c(h20Xhgd_wku z$_gy5l5+ZGsuo)}R6eB=J2Xv=l?avVkc0AptSd)lV1!5nLZ~*52(O_07%E&aLe`wD zBNDNoa_{{}sX>xdMAz0TE;dq*swN(@ytK?Crbw)uX%$p`QY;dCH67Q0SVT^Nq*#Rb zvH%;QUYJUXk`|Pd6oo^@D$!b?St6g&nJgpaG8I*bP7N#|&6r(MSy5h?AI>h9vo*U& z%6*qS6sGJYmE}^Z3ku6HP+qa50!K4Duc8Ed)*7XG%Ch1KxL_29ig0FQT1P42A*q63os1L~FTx?gfh;RoqDUE}$v0);@{%H)DyYS^;HB&I z3R$$wA*U5dXZF&PLhQAb7m_;B$s;W~>knbj7m)ociPS7w-t&m=xd@8w^ z3+uET8l*+jY+CvoNUpAnUep}EEQA3rD3H56^o`U4VRS_(lV=ZObr*PUMu!Y zLm>zi&BOu2X3Ur{I@%CL(^`gS6c?UQ8ICT8JZ}*Wr7iVLcLv>5Ag^Re_wvf(?iDyC z%DajJ()1PawSz$5-uYZ+X)NH#tO1!1=8fm z%ccslRRvu!+!x>XmGAq?_kHuwyyz>%;fO+-nw=XZnwD2W)pl>1pgVSe7OXCflgSmi zNs1Lq7L?#z%S=GgT?%NFx_?6I5)vqqrLm#HB559ECCa00l4up#iO8qm90-v>>kJu* zGSE;)iTflX#?>@R+a&XFzAI^Hm_mt9<4vRY-J_&Q;s&Q; zj4Wi4l1l=>%P_v&~MxZM}VLaBjs>6(nRy)LKc<2XAYiU?^g{`~(Gh zJN^V?5$6v`L>coIBxq#JmynoH??e)CG3tj%Ko<2pnN_F zMo0UB639i=OO#*+&MYi1l>6iay^ixWH9gvB7L+8?n3F?`8?^~4D+-JHdgfAtos1{x zE*&Eca!itqE)3yFnB@3IXBo_stsY>X5ckK&p zoHbLT9@+-&en~PbR938Zj%u4DH|^ezN1IP(UI}gT9C~OUW;EEJHqc~3W`~!TmXQ-n zy)4&T0KW`thqIz2kH@r`az~t$7u(I#o*2i;^W7+?jicRm*ol_;;RTfor&W^MsIVB> z$MZ>O2Ox$jiz?EWQYGmn#YHQ;qXcsI)pZ6+I}>;8I+JdgiBYVfZGO1Cuq>QEB6?Us zMkFojc77NeQAC7=}Ufzv}TJAMkQ zC6MwcgSv%$PAC|$pj_&rPT}IpCDILAo{o)|?ooLZQ?8seUE|(I-KPt}>2)2!E)hjf z6c`AK;w}=I6Q<38z{R>;?v{DDtc)62=50gqo!+BaVbymhjH9PqG-gb4mE~tk=VHfbYGKShDSgkX-n8gz z>66BWW>Xb$=~zV>&aQ%N#b38D>L-&drYM?qYS)BJjE~A~a!X7pD=Eb~_1tv|QPL{& z$#Iv0LGtL85m&OcH_s|03lP~@7_|h=LaFb~LOISpdW;HH(oR88SneS)m(oW{*vR9P z6eo9*RI3&wiO%0Ha)y&rR?1Vh_QS?yOOY;n>^j;>;r#4HVUo=DGO@vPjIM>nD3OKv z>ac*0BtxDn)zc!Puv{AqE{5WKsO{sUBg#h>g^CxCRi0pfVR_8AirGe^uq+MdPYo@T z<&9CZJPAW_r)`DhYJ)tbK)yP%tZ)g+0BwY=!g84vNv5H!YWI39qH?2sSfCED9E|8O zZXFGx3PN_M^A=32)J}T4P`g}QX?bIVRWy~BkG_Q^eX$wEC?Y7~IP2TXRP5-&zMlHj zimg@bmDW<}^-^+6D#nsC(K}Vd*2PFB;NUJKes_+g<(5P@hAN|~DLIayQjJku(v$_q zODnl;ED2kW;vZeE<7lU?D6a@Fi8eznmE%L@i>A>WZV^kH94f67m+#HR(kJTBLn#nn zG)Si_6fVZFp7c0|dO>I-pV^@z<#5yo+wCNQydQGmq(8yOtshNMP!Bei%Nzly2G2z?e#dBfD&6nad@#j6PC#!+zz*EblGHcZKT%V`S4>Q&wN{7qv5&t*inN}uC8c@t)ZZ!5 z{$!k$(V%ylE0-_1en(#^5w%Jxrt^vmPw6l$#Yn&CVC#rOX>Kb+ZPG`fVAew=Ueq`ZYrjapl+bUapeTb*)Lv|CZqB>jT)GR}J)n^CuA0+Tg^FocF9 z3NQ(F)Ie7AikFcVFyzIOawXGA<3 zjVwfIF!;+C7nVl782)m3I#VF;OSB?Oqs}CA)i6dqM=j!`Bs40n9RyanIz2W~0h1mo zmnvFsJIbTSh1l_O>XBWpTV`T_c%WBSUJ;^$cV)HP725D~a26LUJ5tbf2g#Hc739K* zt#7uBqp0?kf%0^eK2*eY?kE*Du&bbbLPd$P%$Vt5C$u? zVQW^%4p|jTIi=tBsriT`)=7iNX20=l|-gcG6QW0TU`hdKmL>-HF zj=fQ*?hDFjI^(R>=4ukw7%qtFx7HLRwL@pungY|JdNZ9=4BaVf6-(1pc4cvSsDN}S zE-Bu6<-_rc9vC{(j#EZpH0XI-NMe>M8pa)D^yQ<92kr@j}H zzWWw&F?l0O#-pBiRg8AH>xlBJt9&t^ce0Atxy2Pt(jIJ8bktGZQpVzJN1sZNZf949 zJ@0aIG#YavtHx-xCsd>$T1RyLs$x6`uWC@tkE>pL?poENIIpatkGWL6xL$BnWl`0l zVo^V+ig{PIR16i7_eOg-TdPTt)=;T6%w#yJ>U#}Yh0VjNL?4`N=y z7=h+wikA@Q%Ku-rU9Vl_&L<*;uY z;{eC#;20ep<3Pvg>KNS|qq}4DaEt{4o)++ofM*3fC*XMjF9>*1z)J#N7VwIIR|UK# z;B^6S2zXP#TLRt|@Q#380^Swyo`ClSd?4UM0UrtYSimO&J{9npfX@YdAz-(FF9m!h z;A;Wj2>4dOcLKf_@PmLK1^gu7X92$m_*KAf0)7|phk!o?{3YPO0{#}TN1*61CYT`@ z7t9pQ63iA%2<8ap6U;AIl3=c2Ed*;RSS!Jj1#2x>ieRaNwGk{$u(pD=6Rf>p0l^Ls ztb<@31?wbOXTiD%mM++Vf^`+Fn_%4q>mk_1f?XomrGi~1*yVyjB& zMzCuHyOu%sNr`W#48i?o<7CoAL6V*cNYo7#Bs{PQLTQU2O)wEn{2dge&rV1mf1?C5 z^h=nb?wu0Ml9ez^>{bbqXC@@~?v*5D3fg2y)1`45s+opqZeo%~D56&#hBPh{@s}N% zE=g`^k)(4|BukNCY?B0sHU2j40>_YHO-Ntes}V@pwQ)jy^P$nM#qK*aIvC!m3sUrG znnK+dK)T)u=%U69lJ-eJ>KO^mp^YBiHlqpA)5k^`UH>Qy3A;5-NJm$V%39sSt43?B ze>R1L5gg+U1Lq!`x=#OUBfdgdNS5@^J#O^bn|1U zV~wh&xWg}`Nt@JwPZQHyPpapr#I%&SH04noLSHD<@d;we#fMGLNifHTH7vItXAhe` z9hvp-s2KWarfctpCsmzwuy;ZtZzb%}dqTpskm~K;a6k8jY!2?qn`~f1t84$n?W~i7 z8q+UfV;abkxSF^nma_CsTuXco?0t6P9IGP%;e~6W~tH* z*whl+@NLKD3A1z|O46PQNwu0wC>fs1v3ZS-cS!z&3Ysq+QDE;}~A zJ>({B-U!X`)|Z5$qBnfan(O)kWb=69d26+BH)3M#wO4FJLB{U^H4_>svEE(AA&uLB z@w>rBOpzE#6ZT3R(VQl%$@tw{TJOELQB*^N=~!qR>_Y5?I$M*)OlUB@EgGwPCGS;! zTzZFw3i7tVW16wkNwl@1UW`t>#YtEUHg;s9VQ5Yvd5LzPiFU8es%2agwMbED_HJ`b zbLK@8g~{VUEN4Wb-9o}4Vo>;R?IxO0S0jTRw?}A3A)^u%(ySeNZoH5Ma?cI?!;_ATNFLQn)FXxn29}m zhwtn9=$+ynZ1a3{iF?yXTM|xyxVBXMYy54#9fK_|{ufyAk1CwFf8tp8 zT(yw_Y;Iw(v)6r}Bz4Yb8&O9IcM5T0KVhFWa;%VMOpe4wG~#=2$m5oGiiFDyt4Em@V=|qy*B@ih+(1u+k4BuZwqPNjePSL(s)P0q5nLWx6f{BW7{+* zuI}PLEKQ)N8aF=UPv1$> zdNkyfY2fTVCvmgoMT$NRozV|zX6A++Q#~6L>jtXn(~#?FZo`H4ZlK?dkRFuK_12Id zEGt2)>-o8|5{yGr_)|B7O z^V==9Ve03zz@Ty6}%K$IWld$Vq5RH92I@ZwVw8Unb$_qXa7GjiuSJ~&9RvhiEh+--ZwSYyzyw(a`fE)prQZPxHPLnYxmPd zxGyv0pSj!D|9{%TCS7Iw$;AYl>3@}r5B{HV@qg5ThxXILNLa=GnFr1As5YX`|3^Le z*VgxD^t6$&+Amp4*wKGyO6wkclh7UOeKz)gFf*F%3)-i7)qwNbW!J$Nmpw)B3c;%c zdk_qVAf2Lf;Ru%MCIXzn2e_~XfgMRui-0dA9OSYm_`xol z#0R=YTLiX~526XqVPH+*yqXbV9yh-pj!Pf|eLC}IAKyVB} zXMU&)5Az%ssyXs+96!uullf4WJxuT%!6tsV%f92oUBk~uxUhxT=pU#?;1gZHhhB1?&K3)IESMgtmczlIF;Zu zf@=si5Nt$Xg9r{KIGkV_kQ9i?EK|a%Ec?2gQ7+DCg zgU@o=3h3&x4Fq$cn+qAx-DN!yP~>}%$oC+T@8PmT2u>k5o!|_DGYQTjIFI0Qf*d~E zWkU#VBG^puFrVYXRzBBd!w`&P5g>yf#`*T7ZQx&$GPlz1h|0D zbKw?(3;2AOE#@JY{f94b*)*Q#!j(MVg(?J;D}-EvI|!a5_zJ<`1idL*&BHF-$_re$ zlVCH!y$Gy7U+A*42`(VW;fq}MH!sA#@#B#>yoj$20P0)+q@4{{T z0T=Ef*hFw2!2<~FSPI?EAEXx#xkiX@r=lKqSr&hU`1cqU`Z&Q3f}Ji~&7YtbPm;EH z%7q&KvcS%a8j-wC@P=!2;%~a_ z9R3#Z^lg{Dj({wAhu-dT+41~cnji1EMj!q@rF}r~q03hBk6d;t!37BL2>;k+^9UC3 zPh9pE|CB#@TtaX;!D9sH^FLg+o&VQ` z$N3(YMNDv6g~?pF-87JiCU@B!({kZxQ@Ak0bX=Hk`dm2I^t*5c0=v^pa*cUr3+$2E z(q$i+tz5R-Om^APW@{J9%oLZ+HdC>EW*dSum-R8*x-0_$ZZ_MwaGBX2n`Q=FxY+FI zvLDUPF8kO_Cpge$pP8sKzA#Z`)S9R=mYF?W7;N_P89U5j1c&>Ko#tqQ@jl}Tb0onS zf(bt3DYG}hc!E5FEk5IEvme2Df;>K41qAs7VW07m86+4;FqPmK zf*T01%GW6Eb#orUZ9d}-bB)h<%gi9?PcVSsAcBJl1{2I6IF4XGK>@)+f<**{1Vsc( z2#N`=B)E#;YJxin?jpF4;6b19wwXmRh~N-{LkSKe7)gLld&fMF;4+`_p82THc;7sp zU@^gY1Q!!rLU0+u!#?8!bEePul$iY~@!(V9^QYAF&nWFPb0|R`!D$3l1b6t1&&@J| z3ZLA?50w7o8t&p6X1(4>BX1y;!Aq*CB68PUVKF_zB2m~RQrstDf%_h zeQlmY5b+t`P}(=<=>$mftyxNNxzG5HUVLZf666uAAh?uZEy0ZhxBHCmiSB!9?)TF+c@x3SKI2!)`zyWpl}LUy zukabaQQB`L{J)W8|4uo6HofkKw||&91ZVh+KPmdBc@n|NKI1Qn{zc{eMa}rH znN9GB&-j~O{B2Gl$Rj8x_z%H_1eX$QB)FB}b^@$?53-X3=0&I^SVyql#~GzD98iR@ z1Sb$IBe;m5ir@|(H;B%_zHv?=P9c**CUy`ZkKinVEk16UXA@lK<2I4l*bjtLeOypB zhteEskYkP@7)7v_;9ej15wXuak>GkC_Y<+7TIDyd@$n>zCYi$tR{6L~QP&(ofZb|A zbS zf=daos5aEnHaM#M0P_}t?LOXtc-q0-;NzXlZ9d-F+(d9U!DfPc2xjWPW zd`$2K!8d+XuR92yAV895tztiF)*S>GeStzR5Zw#(;zg?$L6#pC-m?VH6TCq1BEgpg zUlDvw@D0JY1m6*SPw)f5j|9IH{6X+1g5?slAV?-?Ll7j$Cg@9WD8UedqX^~_97Av{ z!6Lu$vURE7c*V-_8?Ra?_>I@BOuzBEmE$+wurBc%Z&K6Vv|9U7PwpakpWs7+PY6CE z_?%!j!A}G~6Z{v!0s>CpBS2~H$9#c#ZCjUYJ9Z+vKtBp5@mieNRt8Ghp<^G$-k5iCDJ62Vx4BMFueln|5> zEF(Cd-~xgR2`(bIm|zXTH3ZiZTt~3hZ+vXN<2OFB#`%p;E#c>>=1xCPGxs1^20ceZBxc^7LA!2&-&(CX;t zT`6x@tF51RGoL}Q=J|OK>i|FRNiTX@i~YQp`Iw*gp=ci~K+uVxkl=Vf&op-?@hmHk z;JhRrwAvB0XW-WTZ+U%j|M6~8o&OBk7xy3SwjQ%7%l}@@ zt^0o;`{V!r+?MtKqvNDH|4XZ-|3fo>?1Y(t@P4M+7xy0-3;k0paG>!PPo*EBhK{uB z2L?0gH>CL_FlcX0VcMG0lbkFv{v97Ke`oq(o+IO%_~G(-C!Z+OtN9QaAIA@q`P%Ra z^6BRz)P*f8MqtJ!cs#%AmDh$!1`yfrCvC%D^; zw`ON-5ilupll+P7$%rZRv)YgYNmy?yI1FnUiYd7DRb{a>EQ=`az>M=Uw($={!?pxNzYSjt=;&vpRKjtq4=a)>tlKzTx-2Y&(XEkNAxVNwLYWg zuv+T_dJd_z-lylHTI*AK9$RaDf{!yNV_DmBGlCKO-dgKTdTyR? z0!)F4$jBpL9!!FTn7ag1D&Qzs0aM{rmudrj2i2`%o=M?R=|pg!IP^Y6H|6mN=jSn0p5Ey zYO$^2h-tIUPFuyqX%TTczJI62dTerLR;|slVq@1CD<)~{1k0f(HghnX2uH&yFdv&6 zhSQJ?_vyY;bzj!lw{3v- z7`s`lg=FOT{cVghR{1Wkf=o$TW8Jb5(laCWlWmLwnXZ_hwN*@5r^B~<4fRWLJP%sJ zh4{q%Z?sXSDH~OW9ibm-pPD5LTqFw|ry{3dWDWEV?tlY=H8v!7TL&G2TScL2<;@$R zMMhS@j0j7yc6WyqpDDSRMXZZSLQ526fE#eMZ^kjb6_d~B#d-u$Imox&GpUBI>!4%i zc5c?#C0j+_Ax-Hi3Bt!(pV@WDOM25NA6)VI9P~_L%o$PQ3r4#_q2B9udLP4P@|b%JDal?0*P_Fa?4z(s)Kb%dvGM;W+s$ zN5xt0^=m5f7T{Ct-)G>$=SaO9XYrS)-oHX_e~mNw8_fSB90WgMUw_6@zQy_d9nRhF zvAiE4AJdP=^l}WJh~ZN)4CEE@c+^31x+`*;CWb#FGb`B*MnwDiy#0&v@!!1>`cvlX zj`>J-T$yjP_59vB8NI)Jubh2!jV&;zr84FQV_D^-VLnJ=evDnRSN3kY;v~$@8?KlB z^Mi}cj$ThY$&B+cBk8NbTASyXnK>3Iqvcg_V8F!Ts@o5KbSbOM=&jBoeZ!YD}c+EXp);dZrBwaFdgR-jj89`l7M6{TN z;q|%Idp?UzSh+H>E?9Uvw8dvG!(h%7V*rEu@KfgzipmW{yei#3}{_NkRjr zss{x%BN~=N^*6FCBKpZLn$$(Jde0NV271^N=X5VfWxe4re2!%q_~wee8gi2UFDq!R zwb-V;o72DX=0rrxi0EIZCC}DviB$HiADFB^*lYl_WCuZj9SlS9IgSlPiu3s&9?$=h z^L_^Ap`$`lkKHW=73yjXbf28H6DK&Uwcf3UV{t)zEI#))RgkUXztg(a zVI6eQ(e0eo*!er5Ro3hp>-ILrnP{5boc$mwIAdi*6LbepHWGmqYb`spiPKK@Q6nKEGM!t;YXDGh# z%W*QGk}+PuN{^Z>hXX~B`0$Zt#uzj@?AhMr=pFQ!qGkneRbyWu$NL^{PQ0f2M{6Kz ztr~llr?@(JYAZng@9T9!@$~e1D z#@W#}o}XgHGER?(9(6J%5!a`0vH#wyxTEV7ceJMzQqt7eAMN#RfK(fnN8ttK3XId^ zNi-y5U48~L&)5ma=^E9nMAN~ZS!2&_V=R~P4w&=pHPA*^e`HFEo^TkMwgdi*YwjBO z+M`OswpGKfxT33Yo1xy$sIl9nw9>^?!&a}x3Q5+^E5!C_F864P>(QioG^H4yqixcS za5Tz-b*Jj~&J8eu>Lo_i*tk?J!w$)y;?GtQS_8cT>Mcng{(VM~b`i07vwb~oAI3$* ziM9BWiV3Q9C@O^!`|eqCrfkNUGIj5nvKg{4csi>3vrrkIhuY*qoG}-{Xnf|fOHi9! z4vW|os8KG5)$B^Rnq3WdvTNX8b}hWfu7}Uqdia6ez<}Mz99GTJ+0ASayM>Krx3WTf zmb2U0O16ouW%nS~uq~{XJ^cJlx8cXw$Th-X)^>fii^*U|a0JV%<~b$2;J% zT{2sN&UPm*%cStikQ*;lfq8e98jL3*_B(OW?GgK<9TbgfY$f=Z5V2pwG)46&hBm6i z=Wz7#@d}34V4FHnm>Q&P`ec;5SlcqNb-><7CH?`lV;@3K_7NPyK88u`6F8QA3I*&l zR8n8S32Zkir8)d+t*mWraYY(Z*2(M8N{^|hQO#4;SEq>B?7tRgKR&7^BV4ls4$n$K z%^g7&Y>BQ|NhCynRO8A;ByrV^MXGzz-pb9QLNS*Vy?asg*6b~M!*DMP>>Fs$zJ*@w zI~c{jha=ffsGxs=`Rq6B`(Lnx{S7PG9yrY~;Tpq%8x22fGLoRyaFHjI_<4FfyMiIb zz?Q2_z413%fn_8k{UBbUc^E)*i^hLf13bjd&6$*(8GBcUDq?Snh|Uo)a&m0r^?k`VOv;0<8zvwOYj#}#`MWuF|sk#7M-o7^H*IHi_KLfbJh-w(#bQA8f z)=aAAAlagb7(p6_5)P&WH3c5QL9bt_G<}Ue(Avm=9!4f)7+F~OT)w8E!q?R=3)9xt zsj#8K`$8L|9~Ryp3m<@m59254!td4-e1$HT?1^SE^H5?YJ_ql>WrdzuajnYAti@t5 zHHWAuaEP?~n`C}Z+*^Y&5VDLzV4yJ=CL4#sR3iuG8bcs#90ujaP*`pZ!{#5(PuB7| z7Mo9Qk=_5a`NOiJqcapkZmyowLvUH>H!FzRh)k88Fe|8fwL>bl4{7b$DeW%O219B6 zc9lXPvlVR?=!vVi{Vv(wxIk5*YEx@hH6)?6O6$~1Ii{|QvIoen8HP$k>Vv^C#vPWJ zUlVa~!5D%38U=%l(Jmk zdcoShQSWaCS!vc9Y5LH>ok0FPMe#T}^|tX$-1}XbWAi~YBg%3da*-^rVlx7c9P@8- ze8GS()`H;P4axBbd^L8L9q??13)w?Z%=ePlPr`i$8By3Tt zag%37^$pko_g=Ba0Bv&;QRh^n+M{TpD5?y=UCK~9JMtoeK<5d5BOR%XKaVd01aaas3j3sc8Q3{6| zC!qEygZV}|EHjqDYGXN^ZmfVyjFq@bo(K;ZC&S~$De#i93RU`Q_`o<7zA{dO?~T)$ z-#CM{HU7i0jI-FG#<^^SaUPpvoX-l43)m{-B6g8+8N1xLoZV_%$?h<&W?PLaw$oU{ z-ZWOQkBsZs_r~??7h^3hC>8uRZ>N5{oME%H2a?=xXz};VP!=q%lQuZ(pk-#3+@w@R z?9KRU{SnoB-i&qf0J+n`Now2#t&9!0R&9i?7|$?n27~U8>1lkE{_i%6GPjC?$yr-P zUT$y)Bxx_N#Wrcx_V7lqf}51n_q0{`hYK#g&C>2yd?$llWu7e0Q%d@A2adU^-JC(3 zCu~!_d3-z0<2%4_+zIWCyKw#4go^5J7-4M2`Fsy5sT!Qex%>j%;+fbpTIdGoc~(yI zxiYrk?UWv>{!(jKsA=BFdUr2$G44afxDEBk{aD9Q{35+zfOd!7PlcRZPe(*VC!C5@ zD!yDbIZKYtiCJ=VzNIDN!(bwXg1RJFV|S%(ZVTyqeUGAOEXti++y;)w4wPi%1d11G ztZzN4$}_MHryz%^p{F#`+X3KWZ?{8d<53*{$6$c*ILtP7qLzFDh5bo5&v*)Lmq+0S z<2i^J&%-w31?0w|e3|C>MQGj;#}`s1Sq+#L**a}kG$B|gAw$|EwA;i+Gon|IAnh+i zTCz*dXGPtvPQ#_A&3GAA{3|FHucBDI1_j1jD8PNpwz_fi^iYk$?LjbLZV_#BEL=uj z+yVWA@<9$YdVJk;a3)dLDEdkA#MZ>NZF^$dw(aDJZFAyGY}>Z&WMVsW^1ff)Tc_&Y zbE>+ktNOq0UcI_^@4bG@Z3uh2lw-rQCcL#(3Z@X9(NcsjSy+uvpyC#q6ieU06l-pL zRdwCd4e94+bSsFL!rDx@>nxLjlmtFKf&~sPcAY>CD5Uv>bQT(TA_TfAg-uZr+$-RO_xTG~q$Qw=Z2C0R3Vzqq^o`iG;-%8$lh35owBH3CjZ)6})m& z?s)I_`?t{a;tri|a&Q%}a!Q*-+oc`&#RunPFg97xK=b1q z_;srBE~g2=4p*uy4ry&PcG_@tARQu~E7-3L`K>qD^7G9T`JdM5u~_1=ur^)bbEX!k z;tS}LTAc&xbTnZmeLXWwv5SnHc31(;fyt@CuKHcF>HZzY><}-Cx(u7is9EuuI}RTU znOLb#jk1QXVqRqPp3BWG7GuN=Fyq4_edI(ieI|sOAJOgv)_`RuOiK@dc#GnZn>HZr zL)951^2&dO=f6j@$KXe7Jtl9K^FqZriu%gs6)&*zUZ1tZe1C#)S|q~uq{z0s#k6t? z;i>b>?fJHR_)~8q%IL81UpJdkkIvjvBkya5JLG6$y0-L&4259ZcDedwhR>WfD- zpF*#kLT}$6OAV~-eU3wLGIk1;E4!Ut5TGtWeVDz{u3WF{^8T192Ne~R77hD5L>x3G zCNF0up}Aga3tu%t@O3|}wIK0cyf6n9{qM7Chrc-gjDzJy(NI+lm5#}xCVg3<%?M}8 zj?X#XLs+20t9#?X@=R45&6r#CAcbiN-icCEo<#4WPxi@mbalfF)oT<88@nY z4>yj(TmOE52=U+{BawoCx!UlObygEQur{gXg)Ugx(XcR#5Rw1Bl?_vuOP%~}TWgh$ ztNsQB=Sw@0pM^k799z&3*DAcw#JZ-mdbN1MA)L1}8qjhirQT5|IJ;q#Q>M-ecjTqL ze+|LK&p|Vic4qp~z0B@PI@MPxigRO&{+fOph5bH^>n|QJCh8RX zaJ&KXyV-7RERZzX*b>hi*|b;RdMEw&%t&6QvbFZz=5g>ag!oT7W)ts3=j;~ju&EaKH@>;;61+7BJ})Ix9qGo8%*XI z-BC@1Xf(B*UWnMp!jhkq0ZlSR7|s6P@6#ZVXnXSSas9Q$=DWr#40q=Kv8-Z57U$^e zEy{KnrvYohoD0#euBX)5K$dFb1>%ym`$lQ`;1rU@x=OvlO=@6y%dC^1ho*Jti@q6q zjPExWnpV~T;Q%i#MAm>IkPFmlDxs`+_`V@ImAPk77t8X5fu$fV+UkNaXYz=uTgZuF z(+^_ua#Pq4G$hCY(#&tzS@x8D6l2e?;`v3*oQsSc{s`KwT6-8n4t zU4b`C|BSv*$Sc5K$m1uxx-E}!|+w6;e z>&MC%35(b=`PUFz22Q?fC2WiMpjD&)+sJyC(y?S*LgiXT8sa`{rdCU-Dx>ggP0`qI zXT3t(fT?9zE;+XB;`APFafd2%Qrg%ZEPsyCIJU-EvV{8Bp^~+bv@ceKq>wGMv zS%tB`mJ0bZoY1IHkYjlnXrVZjj%{_&Ln*_AMApcSYRQj$)QkQ`@-lA|ifq@x0 zg?0utt~k{)HsjJ(HsJb*sI_tZ#C0ZaWOOE4$_CXub~!bl4i=g8WSoK%D$ancnDl8~ z)&ZPP8m*e8_8T8V-!y81)TF1}RJWmZGo>*ZRvTPNm zITW7Wf*qITjwzoxgH4b*gCmg1fayJ%)@s^4t~}N90&={;YtcOrmfrE=a1X}c_Yu#) zDsV(IHS-d4yz*gq@BNkjit0b`k#WuL4Fsfzd$sV23 zM&&unSe#LWavq#m7yMA5l!~~tuKvrNFZN#wNW-=+-aUg3@mJKu0RFF$NC*wMP)&&F z22Sc-M++Dl4v`eCeho%B5P}Y{Y2rX0wTiY=&B3~|1E8oww!5uSuzMJ#*#f^D`wRO) z%GzQ|O96+|Vu!8vMGb}bGZjA6-}mp8x0c>Ae;l(oY)2B%kcH;CsRTQSpJ>{aW*IWG z+u6Oq!tS4Ql4p z0L7hvtfCcgX-v#dI}#?S0LL|I3Z_z{(TLuYs$k1DD(*E^;w0;EIS8dadjT>B6$)uXjC2Rovnt!Ghdx7Wu!lPR3T&A;P*eE-=RhIYCXj$)3 zDbFE2)=9S7EPmCV@wa{qVXYuA(fa(I$M$2x4@ssL!UzXppW>7qw<69Ep_rXij4kB` zJFvWZGs03{2h}D?PSKjNu?ZpMN(}+7In+QE@}8M4MHokor(A*i{7a#XCKS!S3gflv zyLeX7b_MjG7J19`%A>r{d5fx@+Jg^GnXB&Vy_*<^c9o+U_|kL&=^N4}8JxOkYmslg{Fj2{hEk_v{hLRefde=0Q{ zTG%k$G)4%O%@$%+n8`%iM0d9iK{W1pRn8aJh-ui9c*Ij@Nm%JEzxg3EuDK3auY-{gm91Z(uY>$f3Im2uv1iRu+Xz z$(pKAU|MnpG%v&d2DJBVu+|*vGFtP)we&NNXaPLch|n}g4s^7leW*kKX^=TYsKTgg zQS`61&$CL;?`tSst8T1TY*Mx;-7u&+ z(O@DgC+J#XkiD9YDMOlIP87u-VwPB@?xnV~No!MZ6nD7Nl@I%>sTlf7kx2lmiZ?v+ z*0cx7Mwcg)V=74YVJgq>!dsJ7GL3xNoY*iVzcvY^jkmg6SjmNC8aRVAx ze<;Y~kXzx9^bZe&HpPq1#GD%#{cJNS#(9Zii^rRcj1q(INLD$plimU5*;?hPSx+6H zS>6BOQmh@2%l>LFNk$`dR)8n@YkrK^0;PilgH z(|R_(#t%qX4q}|}g3@+n?NMs^OImeTsw_f36^=)&OhZ2h^op>v6*ZB-@t|tM0v1g$ z7fc68pk#Y%#2G~4hioq&IFvPkm1y$Rkrlp_JDyyZ@cXB+gD&wZw*2BoCtuY59#jdd zqUdU;>%i*0q2(7c~j9E(?0vne&CvTr9K-^99kIH+=8Hq=)Yr`@KNc zo``YnyMp2ID5IhEAPuj-jSKSopR^~7&UDreVWp~OHbuK zY3h?9djC`y5-K6=fY`De$tA|hj)AfI)N956Z8>ZT=T!1*BB>!=avk0ZT$^fQ4C1Gg zwDXoi#|`|gy*PK~kfX0z1ZGtC7k)P9VZ)v$-}onZS!MX=4YmbQ(pU+J#Kn{mo{E_* zhu&yiG6|D`Ewbdd&5C_7ByxLdKYL8>bd3)t>+(|{N@?2e%xwp+WJ34R3a&+qM{gVzD8FQr;bHv z3v$SFb+Os0LK4XfE4{Vb!C;l{4k0@ssAqgon%4to%aE-~zEJ9#1btDHpBF_bj{y&o#$>ePq{gh-E|w-IhSD3 zzuhEAOx%St1b}%JpIz#|Cl{cfH+(3c$`^6E>FY2N^v^YH4#cySMN;@aGb4OZ@TQhT zsEiB;SLU>;3diu6uCCKBs+$=>U@3{jT~r7d;{)+{d}pF_yWLYJ@|&*;m(3Ob+!|cy z`EJH;Mv8UeFud_F&M;Md6p%T!B`UYp#M*J_&D0&h)LK`nTU8O9(S_&_r(_;2XEkMP zmovFKM4ck(gHA695wjSqtV@^uK#7SCke$CU0c3}NhW+ee-4{#Pj7k$Fn@v4_BK#YY zjl!|DHvW;7L;#nIn)8zZttzhqvm#m>>s zOE)g&GEpJTCi8oSnpcPbSsi=4_s3F2lgp8~yhOX;(=GeK!5WBn6d|Nq!pG_!yUwE7 z1Cmq|y_k;Gs&LUfMM$t?AFR&3UMagV%1MGx*rzI=rFAAt;Z z%)F!<`?5%He;3zB<2Ts(kduI4A|wvnT45kUBxUpvvnZ>=Ar6B_UI}K93Bl8gV}X+A zOWCp@3CKKnDBGkqsKseV3#I;ZX=M_z%F_P|xezpixVmkJCmO)MlY&=@p8#L;s{UM* zy7>Z{7anACwiU&^;^y`jM?1?J z3`6cLxR>O3J)J>oxkPbU`cKO}?)IPcE&N;B0*}k3-scr7+WI>7rZg)aMiw4(Jc&NpdB!3>(;wU?^>-(-3ZNVD+6-ge4Pe6KWf%E`dgkLj$1hD ztDmYWdwg+t7Q1s~8?OzLE;|(WuZ1FNzBf~vODK9i?KQHe&kG1|o{j}SwBmnEPwAhW z8>ZJLJ!|rqV^0g>?(bMOl2vP4Nf-F*zZv52rqmjPI%TC;^g#$OtwZ;e3Wj=uZsTmE zG8Y~s7wumzgs&|JBnwNPc6mvM$VMb8ld|rJG(mPCFw;{GN~OJ+%S}^FXm!JeM7_4u z>bK>6o&nJ?Ui!WdN)m8~-{45c~k6gB=-i2Jd!VY{>q66@SK69NB9w|_BDZaSZ~VBDhGyt~zoFf)@pu+bYfjmaBTaLm*c zx+y$%ik8gmVo9WNg{@U`bov}}D2lS&OrxjzX&%24R zc}H)@(JF_pDYaaH6`}lAdFhJa5&P9>F5y+ zPVnfF4X(LogzOzsMNgm~?cF}z{5lZ4m4_Y$E)^O~4jGpo89|4}M}bDGk39iKKl14& z3aVCRF2OX)j#mD`47~u6N8O~hi8yJCtbP>%*<{A}Lv}9h5(ToMW=QZUZIlVrsBBIa zjzv5wVc2mWibP+Axf)G5T6Uk(I4j-Cha~p6I(=#*ipwV;eIhL4)h8M1R*X1LJzUJE znDUS!YRIRM@<1T6*e6aSBGLx--x!ber>0#h$GJXdE0QG`0;3N0iW8T zuQnLr2kr`b9)fi!)S#G;PN9FNDbLgA`7!NxUt%#ijjCuSb~oK+JiJdtbgB0ed@B{L zn4Ui9*>u(4C7kF_235~VAOjC6&AAySIEk;=ql;=rS(B{L$U-<3 zw37FCzZGR`rG9V;19|LD!-z97HoKc}{UT~-(&gotURX`NhtQDaFrjeyDQ_UO@LJrS zf6(-9M(TQIbIF)Vi)g{PR|$8$OY&EU8+8q9lB}b1uDxjE7cV^BAj{~MIgvImB+1~t z#RiK&J~P;d!xJ=QgaM+@^*{v@)vyX zG$M$gX`V%7WF5A6g=OAna~RB;mAwFd?emQyUk_;9g2@Q=#VL-XyxjKV&AQ>ysjZMws)84kx2NZlGc@jjqUQ`q+3rrW_8?j~(lY-Vcoki-V2 z`9$Qm3$sH!?HOKKq1EC25QNuMv#nxn3-hx0{CF~ceQsTe2pXFyGT?mrvtEBF8Po~y#IJf|Ez?dPY)7fyuvE8MUd0&O@O_P%_J#V8u*pU> zA)Ef;R^W`(G0v5)dktrjmECs%a-#4QoiyOaC9-Nht&lu!`GImhxwSp6?M;L&K6 z`wx&HOW7K6Q~R7dDc(1U={#ZkdFo8-lV?t`vf?7AJRIZ3Gt;Z_%QKO^5$Q_}{_kWL zSft%D?VDdHyF%=(Oy`qpEciqQVyrXnOSWhH-{6}ma3B8VKivfJ1T?j7ts2`@pV#NT zMcVDxj#3ffS9gXzTwKcI@ri8%1!i8-T49o`bzB+ay3xnd6zH8xBn7P!qKTBYLGT$; z^!MtPZK6Vxo$5NKy>17eI=0Jp{cKtMa(y8JB6>mv6Ge5>X;kgeOQai1vvGP?0sNYO zQ&E=Oygu-XwuG1?e`Rb6?+8G!v_RdtUzeQutyfz8O4#W>wdnOawKG|Wxq&R?IR?I- zV)@@4(gy#WHp)hnR-lh0+Mo&%$wqaMQ|V=rBcpYZ3x+u%%|@-*MLAK)MlBtPnbvfu zaAvo0oes>IZo5fv#{W3ff%50XU|DDjhW2T8r4K@OI+<`_4D=(|WFf$i7J2~*i&=NV ze!5|J`?B&}1h8IDXI2%IF0W5WJ~=K`7DvrIMf|Pbgy(SpGxp+uckHb1+`vM1)L|?7 zgBZX#!mQ+^S*P#q$x0F~_M3dN&Xm%CB#bjUq?{2We^kPuO9V^ znxtXCnXBl_-<(b^_g$AZ)Y8AYvwdp-&FL-1)i%gsPPSjo>0vH7(?hunn3IETFTrl= zU#iA6$)RI0f1QH|ECMTHR}od_BNAg98rlq9-WV9ci_Yd>?eW zU%@@?bPa~%w$7+^=bADNU5511%F0EYOdH)By*hnGxu(#oCa@*ntgL3&LFr+xFBf@E z1oB-}^qRH}t|pl}A+~=+rx`3ZK45mZdw%2p!+U%om~{9uS$n~~0At@Yuhq#Li@k+*E1sG8 z<0DzQd{TenQ?+XTa^vfTVU>x*ll@3$$aHFRI%a6>J;QO-V9I8Cp^;5 znbvZscKu3BDM9Upv`)7aKFB*@zA1|}YAU&MJF4mMLH!{j=dNb0d#r{m$HqQ%R2CZ1 znAVMf;qXm?$Pf2Xzk-bvU`LFO8A5sT;0DgX&pf{YNroHrkEw#Q~n*5co}(7`dqa5 znC2!kuHf`vD6;LOkh%?_%);O!l_f)^-RA2?I}Iv+2)t^qCo!=L=+3IDaT9j><9ML9 zm**Szxb*LQpI;3drr-x$97|h=E6z!Y~KKFh>H3+aDuyQK%l_ z3m(CCya^+X1PGqgzQhTuagrXPV;-?o9)Xq~5oaDDSROGM9zmKOQEMJyTpn?4F9Dt} z;ovX9AurKlFTkOfNV1pE;+I&{m%yW!h?JKQ)t8v1mms!&mE{j{ItW8NsY;2ErRN6qBh`wNlaz3@aV!@@b7O8o5+h|HVW6WROTstX4+xi=$WEoRP*a0>^5}28=#0L|uvDS% zAr_Rsqk&DGOES`ljR+)rC~9OBWJ^s&`#Fbuh{0kJ=#HN9N|w@a7o`qC>2E1%stB!X zg}N}WY-TopD4QKBlE(?puoSUiR$^wvL-##(X#IC^eo`runU9Wz#n{UZ!cAFjqKIhB za*d^-7T9Rn`l1ufW($E#Mn_?;g|Ic95i2U4m6VpshYX8td301v7C4`R{@@^+9{@5? zFXa_krXV>pTEfmI#B!n}b$*`k8($9{X}sz zmm?+0h@h(wX{W1zEmHDeA|o?UE;sXQAktHuyVyi_MrNX4<)Sr`VQpkT&VMdxNGVKX$vgo^T{a4 zT}7}ovRS!FOf(^Y$@E+nItE)|y>-#?2;2;&K>E80FtSxqG`)NWik$c!Y66;UCYBiL zNylUQksKcM5C=0k_U{>`;$r}{dTJrN0c-4LBdv}PnR~s44jIM}lBo)+$^$w^78)3G zAHPR}gzlW8kg3e5v_#G#kbWRld^{&Z%5_SnNorEY6{Vy!qTS3KO4i_bdMcNZ%UsKC zn3rjMQ_wO02iD-I7+FeO4xGaKL{xksUt2NF1T5&8Sfhj0)nDS@V3V6oWZ)AD`H^x3 zWb8N1PMZ1kP{mTySu(&shlZc6E`RcgiAbD@Wf}ggTx`PCu{-G@P+?LrnA2}f=E%`V zkdm3Br1bt&I|#U!nqUU203AwBV$DlvX{I$e*HYF=W6*6AX6$?%HH!Y}lB3mLU*uq= zC`q&w^jtQPZPOebZqxebl?HS)#jUyXhv|ZuS*cuBMvQXNWMgtQHw_sDMTi%VwH8Fl zc|(cmetrC#lKD{@thkz){7>lN3sX+(R3ih6M6anCT1wry9wSr9f8+D>`;lLRBH*7B z0FlQT8r@hp_DG?Y;Re-INYKxFB*v)r+Fm0MSHq!k=D&^gwN9GIWQQ1NfbrUMGe1Yi zGph+C!*+?yl9Wmv#Xa5zbc}C>BL__|MnNSYPCM#dr``%Y_HS+EF4X-*ni{9rO*3Ur z5uxBxSVrIJtgM$}e(P*A7*hcJ?CiW(Wz-~bUF$15 zPxx={m4be^*FkK_5V3TXKv&W#O5p*W`s6@$-i!ccB~7hd615$valw&%${tLtuIx~9 za#pGy;_~oTiBpiOxe^W=&_r40_4a^CcNCbL`uUU;$&|qgdQI&BGjy&Q;Fw9fs2V?R)5m*wfB|M+$^g z?i#;HWoc$8`#g1lV3KrngI{j8v~5CtR|#AtDX0q)hg7-=!JqOdpBR~)gkyjG6BqvB z$3z%_c=3D=ph>L^nAInf2jxm2y_5$wRY*_3(o`bg(XSx$n4FB!jZ!%K9Y)fA_UGRt zr@#iiknmrlUtK?OJE@H*+Pa|h*J~!uK99EbZ?0#{Zqspq0JXk z42~+Lu5l%)atcAvL`7RAQ;%l)OUNiSg_C$=o9Z+pVayJlQk?9X@|yUL8I)$6dJGF? zQ7cYupSz_VS2i=Hpg?tS?l(~*eI)KgM2l(iHhI9`TO@Dsh}3ag^l+6treq*W=>04q zU-g_zDbCSR0S=7b><1Q53tIvaemf}bC@>O6(Qt#TOfWKPg}qU893|U6bA@cE_*E_% ztEs55brzc=1VmbDf~69CfF>#mt}|0pEgUw~xc?meb;z*(B<5Fn{_;ekl<1@=4oQ=s3O3}V4!HUdpD8>D} zsT`?#R6ZfgV*Sw8tx#fQ79XV~un@Ixz<^o#MXvGBjY@;O&_1xCbacBL}w z`my-%ud&oyn^}{zf<)C*NObZ&I64vk!hFthUInVRa14rhD4KcNT$L#5WAJgs;Ps^V zM}}zz&GjAhXx1n&tq}aRIqjD4(OF#-Q5*2?3`FIV^p!V=0HjiUP7X&G%FAXI*TBCVb15K_irnOH@Z z@IRU98jaMHI78tmh0Ip_CSD}UEX-<_hokBZLX0R4af*6jo57bzFllO3N&l4ibFwRH zIh}~-h~^c{5{n`uBYAX3%OmD?UGl$!26$=pt4sw-OlKCp;OlVO$!Fh#e>$%aybf*N zeGljQC3NQyW4#ap>GH$#n%?|@N4EtX!_rjg;B0EqU!BsR6{Wv_og~>4+Kpe}6q44K zD0#4ybxBDvHElwRUU1d`GA4~gJ1a#UwS?qs%T=|nq`Z@=8>0uq@RQj*^3zgPOE4m; zXu%mI+s=!ROegN7Eta)*FG@nFs3a{E+hkOViR!>mI<=0<0OdAhW2E$=byaX$tt-n# z)JLx&hZ8L&cP1){*=GauM@EL!qMX%gtGUIBdy~X&HK67K@#O1ONWAQ2zcg^(OEerh z4Y#HW0?R$RBIyf6KTd;D@Tf(7^41B-GgS22>!U%FVBqfVS626lA|euK?xP|j0ORVr zl;LoLseq({eTjFVk`o38~_y!;<_U>Wf1; z3p5=t!w}BV*XE=ATxfJFHTG393uM12k$d*m7ElH$tNed1Qg!WQ<6#+b%kWoKFsxFj zNkvt(b+5lPO*3d@XDuy;HY$mi#l(K1kR(@%pt3mF8Y9X!lh<7SMs!fP#FmU1-63pd zQ)i1OwsrOMgqEuVyu<|{JAh^gQru8NR{0Ci7~wyvsyG;W{b0YvVPk_J0&oU(>;Zte1h zB-ds!^A1)Im7o#U7u_HM))(cV9#+n>y-LOsrfMchnkGMQj$f zWnp7Fwq+?}y_lwD4V;*!C5^^mBi7WaI!o-V>N+g!i8+nx;X77#W#jMFtJ=oStnBK> z_pIy$2Z7=(f!M?Og|iiWKI}9wL%dyajkkw`zY7soOa>@X8y;r0P?lVj)SXtJk#Um)T z6neh9!2~ehLn}da`GK&(RH)b`(RN0ShAHdYdFcU* zh1F!yuy2FU*4y}Yc`s%rg{Wi4rr9Az_&dRk1{Hh=D_x-(%$e2Y<_B9arpPB{(6WzI zHZ4#zmm4N-dmaYUFE3P6Q|m6p`>QCsrgXGgD6sb8F&pS#^hyE>3>)ZkyEg z#Yl=L6_c}7!DU$rEW|k;b`5Sw$U>BPr{1B?{h)MGE z&pspVtib@`_y2rsJ*!_N#hH6O_|xlb-rJc6JI4eE>$oB~Q+l1pjdd>?>MdL`xMQKX zLOb~FY%a;er?ZjbU%sI(6ZD@F+;iEU(tK9FXw5XP4|;FE-<2dX|CueC_J|AO7{;{Q z{3bPDsE|i}twfzPQ=yz@v+QYP!f^H@nGA?D@n~RA#g5twWi7_TM!bdKn9_`4Wv{nX zMbjKVItms)%J*Eu$v7f0#h?$_E%b{iVb)kQDRZ7gUh6MmonvaLJzF)dGQJuZCKrDd zIPG_oWi(BP^=0kT_A)l|VMQ-y>z|WQo`IH?`lu7bg?9@~!&Nn&dJe04migjw4GUlf?C*^=W(n!^1=U z){^Da-PhMFXC)PmW9N-~{w23M!7ag8>R-e4W(Ad{<)~8d^nA?vQG4mn#QHW5okjhQ z!#`8U_BR(1A1{o(hv@mfV&HvxWL51YR9?A|YZ)5O{+yBjPJk`*IQZH8Urp#n_H?v` zO~Yk)-S2ml@#6tb6?m{C{}jbK44l~Ms&`0{9^H}EpnpOdZtG0LJtNpdtHae))BoHv ziq%(Jo>nue{zbq0MEkvnj|A=PRWY93F=LbLoV4?4zfhj_Bh0q)Fu zDBEknoZCae2mm~2Ze$noQ{maDd=4Q3U^n3QTMF_4_yNBFU{DY!aKxB0jKKz!eT=|8 z0LeEYRD@K7B14$|D_)KZljQt)aVG!1HhBtBj~MWA`Ue z8`=u`35i$mhOln{D*CUnWM{?LJQNI+90ld^mP~qIN7fDYwPAfVfDg(G1y|mIsgHVl z!o(UX^`@Jgclto~g`p1>a7|Ks1&9C`L(fA=K}mTEpmv||C&ldfC2fh$CQzhTqRO*s z+5_ol`%Qv=7mxiEp+Q{_S}{2Sm=osvG?KpxdW7z30#BfFDLevqMF7izgh}~{yG|Cy z{T#E)CLu_+^-EA1&TvANV{(UJGHxgY7WIz+4y4WSAM$rXFK82$ao_Z`BDJ za1JF$#u>GX@(t=Vqh1jH_k;=i^nlF#!B$vOWTBZi!~}Nz6h45yU#Q*3W8~V?5*f9z zaqMeafUhVt8^NdRO) z)04IWce#OE|KVno>z;V0Ob%Yql7{Ze$$8?;GcTk7KHrUbX>)DsonKg!awbZ%Y?$+e z;*I-xcxQs$Uu96g#T(%6H*ObWK+)$4=s4>;@kt${C$X3@&zc-L$)pvmO_?D{w-+WB zm<}fG?hSVQ@IWoO48d<|3etn>#mBN^d?FMbBuwVC_~>!7`taN z*rs4Mfu+#7MBgDP224XLiU42%Gyx0HW=MxfxDvGign0&aeSU}T$eSWJKl*|I zh{(JlgtCO`^0lJ|0=H?;6N5a!YiK{hmxTXVgV26NFDbhmz#Zr>!k46PH!%nb z1&I+?O4jnoZ0{4@}Ev6T$&D1LgosfI7e>)H>=QvJ2tq zuW!nSMF;~}4cLNeM|~hXmHhvtF#u2$Nc#Vu%*0VhQKN~!b;M8u+CEO;Er1G84)LC` zdSd>?2u!xD)ray+nTICFy5R6@>HFG^Bm4ym|3vW*5y(1Z;Ezo46_Py)BJDt4j4Z?6 zN-RslxxM>`HHC0vm%C?XH#Au~UV<^n;Afu=kPF}peeAjFu7unN4VZwAgjV~nqqKQt zxFhY3wT3Kuwc=KmImAXQ*ki zZ`jZW2n$6|`rUySxo-%NhNL4^8)LxJXA0nft|n|v{ASm5{|9BtBK$65Qjm4v>w^by zpe!P7$~eIGRRO%A{z6}ownpvRebZZ4WZyoU9}p4>pVT9K*AOTH^(Asc`K{o^p>~Sj zaQEQ=>`^+UZ;<~VGK;Sd5g>r_A$r5|KlYUP4No5dRDD`Lyb!ApoDjwT+zoGqFgmi< zsUX~rl>xuBRF8OQYrbsh3O=1gHzXaoZ_bMC|BXkeT=M@tSAGX3lpeW9$gV0-0Q!rV zGn^0%gk%?h5-5|8R0hBflruo`fGU8J0sj1UCAE+n$-~JkBxZ97WAnj;B!JTZT_`W4 z2h!7{z52$y-2liVI>B#gGCLpmpK#@)43hqSO?ZUu@*%DieFrVc%VtpGb6}Wv5h5S2 zl-AK6d5Q&5&aa!gu$Dv^DE?cA8kJu+PMBl-5-2q0b%?jpT)%h)exBxQ(6GB@9Kw~Vcv@+ z)WeP#cKb2VNQROUA{^*Sx zeDE1s`wr&euf6(t>EZ9c>WlYfs6$(g0rN-MfOvh#L zyOZmc-_aqrr|79u5yNje{QY|Z1*h{vLs5>!u`yBksw4@b6#c3sNL1fqIsC)>%OuF9 z35Lb7h^HfIS0@y+?d-}`Xoi)sPv4yL!@#G{2G{zXZl7v7xtpO;*Smmeo_w;W-+WJd z_1vB^(VM$|7x=dHle9LScA93r{LwYh~&M3=};s@@Xkdd5Hdh4 zMy%L@ieg4^wO$nsunY$n`j?D&c#HwoRgi@}uSwS*P7ZyH*bOCc_+rh4dBkg!F{#4_ z_F)%E3-zea;}bx}pJR?fn6V}zNDzdY*uj3k=n;kGNjHLKamM6m9F>t!;%%UTJ=h=K z+*iT;Ic~y;?eWjq!w703Hl-E$QFiZR>IURXN%T}e^HaBKj3B_ zX7#Yj7K+&v6yHig2$LI(xyW0NId_t8Mjed71Pd|d_AG96Vu8J18|ruWhKF=?zAC;` z<5DhWh2&acLp6MhefN1ji@lAu1lL?~7Lqc%Dr;``_lSVx!iwgy-eknmQi1&-y?l!AgPj|v)AVaMLL*66J^=|&jd-zQ>6TGE}ZkKXvS%Uy4ErcR>l-@nlFj1SV-HUG$@NWM0KGtf(cm<25dYN<>KGhI-;z^ zsmCc-RaQuOsYok{^DV$ht{$ayk;Ys?w;grg*UhPG-ipEnsz$gWqbk#q*c->qQWRln zjL1a%VXi_+?*fVUq}e@nX*+|ig`>Q-j+^--@b*r2~hR+;Y9 zC$)_)I|tbw=B$!$r`R4x*uWC>%Z+@Q+}dd0!6;mfy2Xl=tY(tMTxL7WG$EzUdWO6A zE+L7rT4G?9N>;w@j_hk{Ukl1~O?$CBaacI9jVj5CC{Ip9gyeu>^I(Kr@pcO2(t#(*EOroy*B5QxTcW>BN&eA6iYZg?=1=@;XXfhl1}yr??|70@GQV8)9ep zBVV27AIV62!TixtlF%td;#t$%Bdv4JG33_J9{-4QaQ>^>vg<>P{nkprNs-4;%BZMd zE##mZ;`7M*H+l!cdfiqT_x}QUK!(4AqF$#DOR-+EC=;-@FEbs}>UC9I5xQ!7<*oL8 zy)x!XVSufNYs9@l5*%CWkMMukK5V=WMqyx(g3dqCCa`VB417*T|9-ostOU_eE|RT5 zScxPUgc2mXg0K|HRY6#UD`? zGC9`K&9Xgf$udb(XHwH~a=kLEHaw%yu!odToDy;J0r@RI3H0pnAq%II-3<%by|9$s z2PN!&s9+DkTJ|6c)-!M+dlq)E=U^9m9)j!zxQV?8_p+C<%%$#H9B+F#!?4A#+_kVc z+MV-lRd&jH^87{raQb7y`w~k(3&`HV>35FOt^_3hjx7doW=8TF3b3k2t_{5W_OGHr%LGZ5#Fe~+0XUL z`q}nV8YcTjFo&D+P($VrGRBLcHD3W;cnKu%QtVtAHhLm1>!FQ~v>F}hHae0T{mE{0 zq|>Mn^^tB$qm5Q#qbsq|RoLiiY;+Ai-r*>YR_~+jFff3#L-Ul;EDNs^cYxp3Wv5qJ zhSIWSmJ(MyS=AVE*UKL}F^uI%Zwta-L@x@m4n(VhtPRoZAoCEtImkHC3xn_*(cMA# zj_8&kdyD9eL3uvW>Y%*Sa?IriEXQ2F!*a~!y_RDx-)K4J^0k&@Zsor*{3_ARp!_V+r9t^IqBDcqmqf1!Y9A7v7u4P)x<07ANOW^hdy;5b zPNd?1L@~% zr~WwQfc$P^y^>Q~t^RJ0nU~sQ=1ZKRdNpfaM8U)zOmk-MuW;7;);o5X;#dgPDN7?) z*Hd}A^6DGys*>GGR=brPA3m6hDp(*E#NK*L*5mXz&6X=Wf0a=F!V(dSe@joR559-C zyTS^nhsCHuv7&WYSFbG2_9gCD+GJarO|0u6YmS_pF-oE@PV+{g;YGcRZl*6$6W18q z#Aef0k5zRwYVC&4@WD=M({W7gYUAbgJE6T8uoBYq=tdRNBFE$Sne4I_t!{P@ULtyP z5S}8sB?ylYy)g*)5UmbEJyBfvTZm#HTtyTE;j4%i24Ne~`9V0J=ygFjo#?GWSWk3C z5LOXA7hO%F=LBI1(X~N1o@izeW)WQ)glwWSDd1K}-e4@zdFW~qT_1!bqML)zhiF+4 zx)3c0LM+i-VsM4z4b*CT2Gr0Ds5Ap%ipff@SF*CJl0f;TgO0p+bILT3ohOCtNUvJC z9trhYWKDE>X{!ZSAatR9eH}51>L{AI?2rl$-Db&l)O!Q2IMtq2)V}8sT^wXrR`cf? zHfm{>%V;4~5m^kgHBG*)Y35e5ZcY(*GDsrO$(UZRw9T&O8!Wx%bFyK5HdlvoOK$EN z4JKl}RklS)+ld%Lb1D%}Cpj8(tjNaKhplj&b{{(^ny6O_M2r_UKZ$O$;T{u8;Bnd2 z>`|-oF^*0d;&yny{8y;Ph|z?qWM?!ON#{@mq(!u4*HO)8+L>9V&0Y@ou~DIA`J3ih zJo?00PAyIoCkb7MqWq^4ttNTZgxqebhPy({6fP=>7T-QruN?2lN;T|0V&4BY$h#&& zWD;GOIo_5Tn0ZKbRwQwFL5R_5j{1qx?LswMJBHcSf9g(+vwfJaJudXpPzuFMABuG( z4#*BYa20t(yNjz(>o~1=je2E*RkW?IBkg@#Jr9@1+0GCYw~kY*SaPUd!C8OM1)4X- z9kabqm)Q%2@-~1d%3FXa%HhsxxHnYIuoxv=?rJU8y(&WD+O$`OPZa zr}4tQ4p-Qb^;%zyYK>ORbeE8DsBUJV(L>S!zlE>EH+Bn0>#-u&SHTD;U;oC~_wg|H z4T&ODxHX(p1yh_FQW|Gn)pvN-RYR=X6~!Wwdy!qq3gP2F?{bS@6z z)W2H_8}$yU2E{v+JTq4&58mh#M<|_;j7@YlQuoQJ?m-gh*2Y-TJ$)Ds^{3(7jf12;-&+rHMvzX&K{yBevf5Ttozw(zP#$T3N@>ir7{;Jf2 zzb^ITZ%Cv0n^GEoOUmYNOS$|VX*qvaI)%S4oxwknw((D-EBU8VkbfrK$Um2E<6lU( z^Dm`G`B&1j{A;<6|0>_ff0Li&f6CACzvQ?1LHRw2$)8JH{zj4%iQdVO6s4u4D(xj* z=_(mYFUh0ylUgW)q?XEXsg*KK@+x^!Yh|t!t1OY)Da)ny%1Kh3vO(&koGEoywn|-; zZBkd|N~xQ2tJGbomwG67N>eiu#c`-qIv3S(>b+NEzBJX{t6~nx+*>)3qXLhE^)&XzQgsZHqKZJ5!pYZI$L~+oXBg zmC}6eT4|wngS1GyTgulSkrr$Jl?t@yr9$l$X^Hl(v{Y{)73s0k33^9qx!zwY)`v+e z^pR4jo+_2;lcW>%`BJ&QSgOzi(n|dVX_dZNTCH!9*63$SC+Sa-R+qo-S3$pJ>Z!mJ?L2| zJ>)5u9`~$~p73mvp7fk9J>@x9dfIb^^sJ{!dd_p3^t|T|=>^YS(u$_ zU-~GjLi#vro%Bi6Ch4=N%cU=(u9Uux+9iD#^`P{9)T7c5QC~|xMg1!MV#Y~ybb0|l z6S(~j$Jt`HVI3%9UnetCJz;2|`5!@l%a0zW4>gmmS5wBK8eS|Moq8q8XYb%9xT?yZ zl@Qu`=mHw*i-VwH6YXUNJ!A=dvJ3-d1*XUv?JK!^mudFCNAE^e*8%w*G_s*%&4!y+ zatr7rw}hT@D@=LokU?hJXOHnAYY;I^XH%5MTAL8D>n_JY2RRnH%I% z>7s>)?UU$Dj)QH-m06*NEECaSj&}wj0cTGBC^GPjbz%_bVfDfH?HI5cIy$H9*82ch zwf#l_c79!`>x00=u6F}n?hanL2XvNuLNB=&&V6r4mixdYxi4hP@i0&JL!q1q>*W4o zXpxgQ*4|L;;;w(aB8BbtC(th9vd9G)jO`AAmhw^6UUJ_apgdU0F)FvKDB}URnRtI4Sl9fS7t_C4fn4?ZktA?@GO{KUQXx$Wv zn_kw$@^LUr zJ|33K3t_dK4?(#AZj}pRue=2Al9$50auGZxp8!wD#qg560$!C%;eELbK9bAfOSuBR zkykNYrY+5DU48O~eHOBdgo~N+Z?JFxxX0vt7`@Z9Gz$J`Kwp z^1LrTXY|mWB_DWjmNo&GPlafCGjx{9}+j$3Qj1GDB%YUMN zv7G%l`4Q0rF_bvj=eHt^%xXF!#p_IWn4Kw@Xn0mDUyF+SI-JAZIEO*BI8`tm+v4=m zkJ~Tnta_FsxW3TwDF$0D5=5-ksTu==&{8+tOl_EI?Wv*+Vdq)INuGQYPJ0b>mv6y7 z-3r6yJup_@3t93$$dl`_U$?panqzCq0TKP$D_?CdNrP7VidJ#rB3I=OZ1GN9mAi0N z?nV`MFV^}Yw02(&2%R-h?U{bDE*<@1@Uea|_*Q-qXX@1uC)ZfIw51i?*)Ik}z8WAu z1buPY`pJ*N82NEb{lew2pv_@LjwMbF59Nl2M@%li?Uj$SBS-5cAb0lxnV^TG$D@S$ zu7=jGLX!3?U1s|urRzC}m7hn+dI34~Mf62pLi6!5OqO4P8S<+zSAHFq%Jgzudp_G$ z@pDAizsLEc1NQx4x^|}ZCgv5^o0#*gmoC>^3V1WlW1`r)j7}6gmn{;-#-)m#6nl#u zY15%z@zO@YXTqTN8>Tp+KTdM{aN7~(BQx6$v&DpM4hR8_@^z$V-jHV6LT?#%WWq8J ztM#>s+OKqtoQU^fg8UH<`cs%Ke+CQW&tb9r1+169gwy1&V2AuQ)XLvrkxBdvcOuRb z6R`j%f<$>tgjTb*7{x#i-}{b()yRY$`IrsTLEs z$|LN33fr}EZag8*5qk~^s3$^(cvBUs4m3}#YaLc;%ibqmq1R1qvz?vjdS&jug*)H3 zIw8ILM#FyjjUBMX&2wRR=XO`YqKG|9B~+={tQ^*Gvob~&hbOdKnP6$VEH^oUwoJ0B z50;Bbd{g=T9wi1IRbt^O zr5(Jew1>BpIQT^A00)&$thLgGwO6{bj!JjdN9oD>E4|oAgB9*){VA0HYj9( z#lp!{nAikz*R2sdYwVTx*DINpQh0K&d_UVO*E>7wI9WaFl@mip0||+7a-2fDNo5g= zQb__;=?{i70AiJakgbfsC-1s?>t$p_GO-u9$_YD!Gwkq7xYHJT!8gNA-X4U}v^w8M zdU$;zJ(2BIq#;_lnX3;zk8N!a*MK;}L~-O)9PzG%YBbg*l)P!(CPxqOu69ASxVCU=h=#QEZmM0_6lKQkJ9G6pPM}aNkgq>^Ibm zdimGL87;%BPb0$%X*Ny@`VYL?=C{mn8)@4jWU;#v#USgDEDFLc))xcS)^`9wBqs&o zDkSAW*pB43AY6zfKL}?bsSU!ZNUjgU$w+PpLM4(NL0Ez0rXVaqk{5)9NEQZRj`hL8 z3?xg?uR>B9gb7II5})stOM;MsWU|$lYpuR)2*M-Q*9-SrpDjF{C{6?)t!7?7g$sUU zl+OjvvwN(8O~TczmwyjiL#(8idxKR`T#~Ru1dl_USE$?ieJJF(_(@h}CpMg|bkqmv zmnUl?n^v!&{GCE7^Kx9F8;JCmE-!s2LZ<_tys{33WIcG5 z4e*Mx3Eot;z(>mI@Tqbpe5afRzbI#;T{w>oQ!Zd*l?&MXeM-0YVdKN7CaPV46LkQsK2@Cmgx%Y^sFiUFyx9Qkx-cwg1SRPLJi zl18R1EK|L*G%UmCbh>n#RoSNz`R0q+9ALdjIW|rS9gmE2k4NmTqJaq2`AfF*c1eS! znj0>3rB#p|dV-4>M{4Ic%kqC3o~=h%#mS0wPMzGbE&D3;aY(4k>uAkaG@Jm}Mj8?0 zTiCquZI2wEnABe)DqD2O-b(nIx-SPIcV&x`ZnN@{rd1AWwm~J+j;fMoDMJrMH0YeL z@b=OV+g|Fw(%z(_Y)_Q0S5j-y$N7Z9Kct`9^g}^~5y}lWLh0di%#vmI*@6bj<)erd zpD6?}jQi1cWxM&@8v4b_w&m)27)6CFyTE;2M?{QdS;A{~>Nk4igx8S*s8pN zvB+B(i@XijD(}Gc%DZrr@*W&e-iLdY58z?tBX~*q6z$Gu@PYC<{Goh-w&q(lPWg_d zE8nwBWRc5cN z3j0je*|+qo52}a#sYY>0HF;aL1s|feSlE6g8UXsxf?)8q3SncKmF$ zJ>Raz@vGDhyjtzd>(#FOakU43R_)1OQhV_?)!zJlwGaPZ?JGsA@lr>%pVUQ7kb0|$ zl21*N2B`g|aq1u`T^%ePrw)-$P=`wAsKca-)Zx;lYO-{NI!d}m9WC9ej**^G$4W1& zd7C=}h!i#T>t(d$r8fmZ5zh*dX0AN5p7R5!yQ^)$?OVVH|O zNo+N6JEpkt2rl*om*ZUS_%2`Sy1aA^C7jAjTs~xNC+iKG$;1QbYFG%__TD z8F9azv%PSRo$Q_3vJYstqWWOIJKl6?jpIER$9q0_)eB&_x)sK%7r{jJVwkF40yET0 zA)s!9W$F$nRd>QF^>SFJUI7=VSHeZ=F4(SK3sEhagQc8I4KbOr0PvLlp5%)-V8qV78t1B3M1887^m)m zIqGegb?707Jv;4>b5DqH-Y^!aqD?`=;-GKPm8}(Sb(Z5A4{4ruZXta61W}$|y6YCn zZg*gx?}QljF6gP=1O3!{VXk^V6sQkkw&92LH{Ol{z0M|-wRO`iA|6y9$J^z6l)WGJ zmF1$c62lgji94WGG<#9pNpDe~j zYttKSdaX^fkXFOZ(d_Qway86~X7>`!_7F~vX7>{vAI%;kIwG1qNOV9ndzxr`H2W{n zZqe*NMB7KRhlzTl*|S9TX!iDSxofPv!MMcQOmw~mY|FlE_&IE>jCn8F85U4skSI1# zezpWi$7(i>um0;xXI5nEoo{GKlrp0?)is*EjG4f|izhdYzmxqrw=1#ceq~%}O-i66 zuF3Pj)fd22UqoSh3EHc#KsWUbI9GiWE>YitD^xT?>N{|w`Y!BM--A2U_u&Ec19)8h z5S~*%g4fiK;XU;e_*ng%De4!@RKH|B)UQ~Q`VAYVe$R5$AJ}~LN0zVtf-Szthudm0 z#4!}1p)y33!RwX0NRIgnuU(n~%;e;|I;1bvN^&yNoPsm(iW6 z&|p)+i-S#sIITZ)(*{6qZ4eH2FbvU#;9!SBhBgdxwBfK&8v#qSk+4cjhEugMaF#X} zuGGfEwOR^RRm*p~99AVJBmwmsEmd^rM;6E}me2zm8OfuQ@pccdb8DyVN3jTRZ{_>!qW54yWFt7$`j~);SA<=jY#ZW+ z?~^suo-xXfL(kxgLcb7Uc}H=wc)z$bSr3mT^hRTeIGrY-Pn;gH)2S^7RV&7!uYg#s z1UhS_&__EFhh7e|v`Sd5t%MEQDlBpfzs4TRJo}(%kk8qs#YsRXXPj`%XHE zu@7gQPe&7QrAs5WgEe&Jv{Rs^wjMfa8=$+k5g2R7$J*`uVgJHRjiZ&At%xRtMB}{B zOEsiBv+S6{4ba_;>zo+;lgJnx9X;#GccCZF{0c$I$516sL7^dW8@K~Zv4cFOPQKTg zcI$p@oqS*TKJr*-g(Ey2M|cK!v@=n4oCRIAvvGFM0l#)G4ARbrQQ8GCQ@apKwXG;c z7oikg3_G+-;0EndtY(6vPMB%G32-3X3&J6bUy&J9FTc1W>`UWos=$=Pe`CStZu_~P z3)ruEIYq9nvOby*<+R_BeWJk&-iPF|-@pt`>j zT5DH9ymmGCv|TVzyA~#CyCGW(!hEd?R-z=V)2>JUyAif&HE^AFGu)`%f>i}wMf;j< z0-Vh~i;Uj|Ipo%b^uck#u+MkVHK4PBgzy)}&~4}%&Jt}xT=hFL#qEdqpO=SmUU*z? zacGv~RwhN7x;h;5ew4L4(X8DCbF{l*k#-*})9%OdK7iwW5YE&dh6}Yvu*9$Y9DAIl z_Bf}8Mnra=UTjMY9T0DD(gxI8<7?M&BZ40Fr>%mI8k`)9(|zq3h}NFP#dr>d|9MRP z$!+{4)O#MGyc~hI&+diXBerOGq*vTvU*Vf%azw%u-pS#Q6%(Jt$#kSA)R?0$Xs<6$ zqi?WIOg4ORMgl1>Ba)HcKt_5K8R;#E(cVEN@*Z^2-beBJ0LAM=7@>U(W3*3TiuNf^ z_Gd6#`vNEXOIV|QjZ8(S;8WZ&UXNoW1zi|6#tji;q+uLplsF6(hauB2vI{bH9FlbzCg=*x(>2J~>BI4UZm+Mx zUXxKgKCIVE-Ck3FmBUWEA|kf#)()%PT{>vBc3DrPWzj@`wFnm0y`bx@p_SeSV)SU} zpvOR8Jr+yyF(45Q{m$2eQ;QPaai#%Qujf+ z?uRTr0ha2Cuu>lg>-0gG-OpFK!&zg$j%Z0{lH)8VPo9jsTP1b4O!TWC81kgNzG{HQ%Bc5FvP z7zMn;AgNwS@g4EBUGs%DG3XLUnPKsEnI*ZM+{#|HPVa?XwDRfXrjQ&bM#!;nLi98o zemZ#dNf4_~hE9404ArN?Df)ESq0fMO_1W-(bVq4 z>Vxn55A^}{nU}cjR*H7l+xsWkVUoA0QO{$0q-CMk z-8#7(`I@r@FE{9q*nH5JV}FW~C09Uuy#xm7rI4bZ2$S`4%<)TPpHRL$q)*uEZlA&g z#rcSjR@N4HI&`U5((0A9j@dfoT=ux|bJ-2{-C zDn=zf+p>OkLU=8PutA0|Ogz%DSE+G^9UhLrJ0ni_TSh;*b6fVAy%Rxw>x?Sgl7wgC z)J`&uGC4F#+xgYQYwu(ii!;dS?iAy3%fxtgLT}QptI1invvi`zBa@???eM7D!@UUJ zAf7)Rx+8;}fedmcs^POxXwF6kITuFh=OL4v52gB6Sg&6UHTpKVOTP>r(09Op^_}p7 zemT6MUkTsnSHX|^)lAlRv6lL^ELz{q`s+b9Qmv4 zEU52gb^1PbmtM#2)%UZP^#j>!bJh_E0 zt_3NgK5;D~eq%y^7F7K?l>6sV|GWS_^%udXzXSvH*IIc(Fvz$!O4Pd|NjB>nW+5%kk{xZK}kKYu{|^dsu0pHM&j zf?2K)Yj?6}_cFWP<9y<^z0elP=~XZ(d@tN?_27}ZbTIU-0=*wDdWKdaQj7lu(fUE? zgtoM&!63olFw&4#;NVB`G!&}fpTtV8TPx>|$n?j#gm7-Y1EaYh`Z8yz6W=!`{1 zhD~jT==62=p7c^@8d+B4>HAI$H|S^WU~U$5Ct;ubU|4kAJA9tohvwPIUVZRcd#|h^ z4_F=Vi5>3+9-}w3Hu^vpqc3(m9y{C*vJ4;0Hxgm7LBG+rmXEe)d6M1Xo^HD(A;Ie! z%^uuDvC*)u=mJCp1;!wpp}`Ps41o^DFnlzEr`nZnus@BNW}o9^(%e&kp0opoyA1{C z<&(MA*f-V5cQOY3JNO;LkYII?6eUVZESLRX$u zf(@YUM(dYyS*(y)+A0ASZU4pEf8p1E_ph49xj38X^7hmM;Msj8lcKO9`tX<(3ALd-< z7a#3h;ul}*T<8~H@|m)*-n%2y6V}H{B|cTp5HizxZjl zVkGAWVVPh2!doGdQ-ZLl8Wwqn*LX+Nct_TFr_^|pYrIoyyrXKonKjsKs@y@F8rq_69*LWw@c<0o3C)arA)_5~& zyz^?j-D<+7S$G}BDtOCS1MeCq!3V}V_}EwvpBWqBOXF1d#yAbWH%^D2j5Fa^<81iD zI2R5Y=QD0x$Q0uurW=>AC}SIIXKe%1o3|H0YOi{_1huUg~PYP@=QpDg|V0*oDG_Te>{V7vio##=DScn78! z@1YI&0HzxsVeh}=>##?bLULXMwxvipqc-zS-WHA5`DkG-xK|t@VLZcIhU+=#hPAYF zf4zFHC=D=juRQuc;BCu3KA`lTdz(C3#F?Y(mD8P1er}U}ce3BH_OZ9gLrIVBS61C7 z50Cuu-)^XE&wwOhU#m$4!+EemD9tGL3;P+mg28@8nt=2-q=`s>M>-AZA4pF^`X|zL zNdH1whx8!QI{-Zoq<14_NFPFKa9u$s^#1`+O9u#mJY_b_NdN$FxBvi9O9KQ700000 z04W!%G5`Po000000000000{s90A+1(ba^gxcyv%p0|XQR2mlBGDHp3U0000000000 z000006#xJLX>DO=WpgiLVPk7&a&L8RWG-iEX0*0tQydJ_G>W?|?y$JKTNYT{7I%WX zy9Rd&?#|-w8iKp)qQNbM06`;rz4vqe!Mio0&OUxjMR9IfB8C{-@lJz(!_ZEB9CT|MSiKf85d0{r_NQ zrSIzgeg-zP@_*F*%>VrVQ?CBUjKKb9ID)<1zuo^|-@m=RA^iVST>l6Ew+jLq9F*ay z%tiTm2I@WQDp9sl=d0Rm(h6(xpZ^j3^{9;>%u}>{)fK$xKDdgiT|r%z^&J>$zVz+S zpNR0N$Ow|qIIjeTxRm6y)b!7pacMc(d2;!=1;sg~*=6$ORh3HR)^+uYB=k+dmS%cj zM^|SzeOG_aVBgR{-^iEI@re<-zG?c|`I)(;>6KN6xplft2D+^s*zPXu+u_!?)01=g z3tb@b_uC(LKkpxYpx-`yf1xIR`Sbew-xI<=RBAQ|9XcMm7$2x^MNSTll$7#_4^TOY zOv+6@;wW1spFk%`dv(=zHj~AoOKUXSVKhau51p>^18~=l`$_NZ1(+!6q9Hc||{-re^vuT-@*D zCAZN|)s`lK`hDZt>NW45#B0>nKa!`s&t-2Bmw!m0)AW`hSU>mZq#R{|@)_tv?q>w6 zK>0XKX1z>R`0)0*A#f z?Ms0x4vwWxHO9{5O;aYW30sPLt{p#mK%V`G(^OuWq^$cM(-iT=-uc!II5i9Gzhp61 z6bwtG?qQ6d8;${b%O9>QdM%pTI>^dFznpwO%D;H&eN)1G*e*o`l8BWnqg0|)bl`Dg zS7;Ck7#(-w8-yH3)6u4LYkvqM-{=K$Or9j2rOlHh!g)*I^!2~{`yr+YnVj3`OAj|P zesPg~ju`$kRXu9<-wUVb2+AL-JOuRR>XEMi$CbPse0sC$9Lmt0`#b`=i*005(@@q4 zBqjr+;sgay$82&1wd@Sxexmn0WAAkbihsi_OG)mc6DyiaFw5lnJyQs#DGd_9SQu3AEC!Z8};9INHYcA;=K7G@866CPF zd-b3z+SEz-WYP94J@HK*E(-=IqH8c6eS)LDiONk;UJ*nwb4wndW|}JZpOb4g9!JZE z1zIJPhAdSkQJ3ZuCteKq5#NY(4DuMOGS1Pf$f*dRbXSi5sC4#uo@oC*`mg*mwx^T-XJ^a3L1SIrLl> z7XJHd?+6lg6LM?>A{4D@@?v$n(9dYNL?IU=>LlryOXAgt2y>hVrivF6n$~XsX!WYO zX?>zi1DoXWk?N-1yRm?>pgPf!2PLDw_4vwSkD}>amxjX(x#5&~O*1#)29Nl5b159MmqK{1B7c!!;Q<+L?yKCt^l-1mGSr8(61jbFOMvbJ1%z5mT9YgJW-(h;;IG4-f*~$FVrA(7E+H+{SV4A zhfw3~Y^-)u({mAlYSIu{rlX162}s2tJa#1=I+E-{?rlk`&A)k|H%d$twY}i>z`n+4 z#Zm&qaM^mj9?u9GzKQ-7Ymr^Z-0z@&ZE~#s6@LM~l%UH-TuMyy*^gqCx-`}bPD}RX z+pRUoJsH^Axl6i>${TdGl{{qDOYzmd8YIBdPE_;_^2daYV{Hw^XuCqUvLc=+ky$v5 zxvJxV6w}JTJLFr0?77v})yRSbB{T>#`D5uE_sQyTiQTfK&cCh2NE|>2H?@cO&!Ke& z^%~MSIs~D1oSgvpytdnEL*LNR?#h;IK%HvS?VBB67)5Vu+;e`X#P`FeV|A@uB*UVaX!iqTPtsSzjn&Ny$vi;y_ z-qTo;?)hqb(0!h4f0p#q*P)HFsApghgVHPenaSeaCp4u5=2 zK=?g9Rz-LDPDuBRA`EI2_RVGi*KUqb-`6=QGg2q?Mf>8QH~aGMEK-zF-?w?aeD+ zExmh#9R{ZlUK-;A(DZ6LFzAHmo89P`CvG#hQhGgf^b~D>8D3-TA;96qs`hePt(!Vq zRp^HQ&U8LCS{+ut?WQ<&oX9n6=fNcu;7W$g=)}!9DFyZE@hlVx2zC^&5<3L{HvSXA zQdbp5QTHxL?24ywp*TAw+*z?(b=0hf^m{4?qZn6DP*`6(GKum^!z1`Sxd&@k$9}-rW05?7&km8 zubNwecUWeW&ZLZp3sb_Sp!-~!?{6O?UWSst$X)8jrE{+hco<*FU>NCMLVv z|9x7*n%n)Yti|jQf9cq%{S~*ua*6sozwLhq$Y<`g*{W{-9tBa_#2DbQ9pFU5#l;j%8*j;#PnaX9`3*|H zlrO>pcHQvi)oIxUq?xJ~YjYq~L;pK_G@goNZAsMC{GW7r>4E|@0%=655Kt9geiGMFo zX(LOCri#B`!R;(wCsvz3n_q^{Mj#i6DE3Q7PEh5s*X&_E{&;y2IbhY2`X7saEGvG$ttYUaHi0#>k|L?xF_SLlqu)3JT_FAf zF+fBdkiJFh8*51_DxVn5g+=O}&8SICm`!XzRKil+&PrTt1E$wWIgZL^L$Yp?H_rC+ z8P4-rHDcN??`K6Qa6?X! z`9RK<^=X%sqG0vY9~Hp|LsrRCna3(SP<41Ldq9F5-26Y?1cxn3XMa7n9zBd15H^eo z8*HJYU{TG0)+-CV_C%@m3`Y~SVa~LljO5%YOk)Ay0JG=;Vw4qPETahNRYl@X z!IDrzmRI=^abhCEVlbvNxT2VWfk*Y&YB|yQ=7hLmU8`N*1z9*}yf?;RC&nDbWs%I6 ze+#1U3R!q{L14@!@R1trk zOYn1lw}n=c5~uh%&-O`NM)@$t`B-?BY&lh<30dnLb)%9E z#~kTe-mVMYC03K;L>ts!&Hfcq@N*TLAL4#CZ`y9gI$OV#5|5dK3j{a>DYX3ae97af z%31hM-9pwrgBV%eqvBEeb&u?osNfC;JW9uHGg`+oM?SsvL=1#KI8|6c0-wx}vN z8qf`8X;BKv20a*w9Y{JD2Wf$Cbjw{ySb*T@PO&}-CtfwDue(L_48=l4$rNLq zV~*L%OcH$Y&=34t3%kS`6xKYC*~QF^`qy0=^IecXG3FE1`;M_Ie{zh~_{|f-tl6bW z{IsWQa@Bfj&RG$!^U2-c_*Z*+zC4@8*i!t-cl+qZ&G8>iLjc?N=TAO=69C76?b?JkSEWIPjs9vH-yW(O92+51amfQVASJC z{lMk&wF=T#;{p!oUdPr}HY_E4mam46QnI9Gv9%AaQ(D@;zFa6%2ZS29_6>UUivM=P-eBP^K9?_FB(Sd#^gU+d;=*r>& zVU$Unvm&sPkZb_$f-?eHgPN6|OLDepzDi;z3pb8_3=2FSJVFioZy7@bSWIkDYL4D>hNdQtocV}+N-qj)0-2=u=HqUIq}A

64|5sdA~Q=i z+1@s>OlZ-{^KW+`K;nsk+v5ncaeE#=S0TzA_k=%)15p=m0iP(@mw5@~PB zCT?l}IUQb2Tw&6@bf{2qao&ff9$3QeNfN?Q6U;FVE{v0^EHQHY4qRcQ80d-edU?pC zAS;6T7K#0iY6+R;95s|yDK10Krg-MdX;{Rgu)huoAHP%ByTH8>%=e$%_mu8A5PWRwH6mAaXsFVS|WcGhe?c-o<$TOSjei1mzA{{~^+4CF^Vi;qNspVjnZMcY5& zWIFsffMOn={mUq!+P>6SL9((etaSu>WpQQ~u_7jm%@v_Xi37Nac>g9(OI!$NG}bChFVsaPQp9dhJQ|xE7jWZwd0j zY9(h$9oZkY_G@L&dBcI6x?$I!%lOMJM@})|w-T;Oq{R z9DyxD{n;U&G%oE0lB0AXgq5+~Q|SK8&cCr8zhCZ^ zO9{p{AV-5~l3ms4%eIdi`T(lPaI=2Q%M6Bs_P@njS5`AE-lL2zj%kXP>@Wi(ykMrt zEMaJ{ME$P~Z%xriqm`C#Jm|#JMsw;Ni#?-ZN%;5~{$RU)1G;}t zNT?*R1IhB_sWAUi0fKYZ>mNbgGM$F12EQkhW-XEAi%}b=okXcVL40tiu}??ZeI9d> zhnL5+?m?by1gi{@jIv&B59Jkr4xj)~R>y0NP%!e>kuYb-b}yrR6|(WE*pAVT_>QPeAlduL=$dj{sD(iX}(HZR1U*SK8&G-<1^hRL((l{LeReh!F|V;F$qXm22u&- zM`$UCyw2GXktb0YZZ19?5ord~)F<-0+yxv09#-^sZ z2ChaK7*08Fy}*@XSWK8!JebV)Df6+7|B=-tk~RA9P-ryDb9#E z!o*$;0C{SDg$^SOzT8c7qj5CS8ig-E)3bej~Z z+AY2u>3r`Ta|QHq4ve$}7F=xW0s}NT9terGC1kTS4E2HpBexlHQI~d{i`>Z4uKxy- z$(CU1i5;r51W~%+=-(Bd5$n^hUQX3(UIZS^E|p#nt!GOnZHVLRM-2#K=#`19Vhro+ ze)&9%@Y<*h%|yu7XRODc$T2K1Ye+OK@m_PPLt_=XfmMQL2srS{Vw2n)8!JUP+L{>n zCrM{b9vaEl8~8D)-BP~q@y(DF{A!2|y$`*&I%wMi2*Lfd6?h0faL zSVAW7dE{%MM=|0%U}7N^qGqB*-A4G^vzTD%dAG6i;8Fg*@2SFyRMvIp^0)XuC54Vc zkc*-cXBJRyCrWS^)qlcLa-HBIk1yrJV`yy z>pYGs=hM&}c)H2mm3)qm$PZ)trhcH}Km8uJJS&e6{n5y$6`YwZ@hTpDa1THwqG93d zY~%B*@qcWU7(D?DwsDS?YwM1h1}H{ zOSDA`dLlq|eqF_*tSPe_M8bI)FeyI2&*{=82Res+H>Lb^!rJ(D?(hM5?z4(*2WR$C zO!#jfg4o}pO;{Az#_CSFOtmKn-TDGxy@H~|8EXcDt{F~??B*hwCx~`T+*{LDq zU{Hxt5`iFk1DOFuMGHh@O)Fh_`)uAZb< z1);PML&5URK50aJ%JV`s)#0c{+#IYl4`uD-oobT!9GtzxkfZgGUrQTrz6@e|qz(6k z>f&n`%H_o&B!ZYeAkMkcYcS9^S^Y?7s7O>L=;F-v%&w9El4^rgd#bryGM%$j_Z$aD z0JF^|eVe&S_Nuzw8yGpRfQmFw-{??a8@7!1ION}&7!x8w-bz?BN^|_KϢs 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

!s{z0dqO=J#Z~0mBsWKS9U8g?dYnJo<0KcN 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�ti*ROa* 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 literal 0 HcmV?d00001 diff --git a/jdk/test/tools/pack200/pack200-verifier/make/build.xml b/jdk/test/tools/pack200/pack200-verifier/make/build.xml new file mode 100644 index 00000000000..76e5f72cf89 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/make/build.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java new file mode 100644 index 00000000000..63a66f765d6 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010, 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. + */ +package sun.tools.pack.verify; + +import java.io.*; +import java.util.*; +import java.util.jar.*; +import xmlkit.*; + +public class ClassCompare { + + /* + * @author ksrini + */ + private static XMLKit.Element getXMLelement(InputStream is, + boolean ignoreUnkAttrs, + List ignoreElements) throws IOException { + + ClassReader cr = new ClassReader(); + cr.keepOrder = false; + XMLKit.Element e = cr.readFrom(is); + + if (ignoreElements != null) { + XMLKit.Filter filter = XMLKit.elementFilter(ignoreElements); + e.removeAllInTree(filter); + } + + if (ignoreUnkAttrs == true) { + // This removes any unknown attributes + e.removeAllInTree(XMLKit.elementFilter("Attribute")); + } + return e; + } + + private static String getXMLPrettyString(XMLKit.Element e) throws IOException { + StringWriter out = new StringWriter(); + e.writePrettyTo(out); + return out.toString(); + } + + private static boolean compareClass0(JarFile jf1, JarFile jf2, + JarEntry je, boolean ignoreUnkAttrs, + List ignoreElements) + throws IOException { + + InputStream is1 = jf1.getInputStream(je); + InputStream is2 = jf2.getInputStream(je); + + // First we try to compare the bits if they are the same + boolean bCompare = JarFileCompare.compareStreams(is1, is2); + + // If they are the same there is nothing more to do. + if (bCompare) { + Globals.println("+++" + je.getName() + "+++\t" + + "b/b:PASS"); + return bCompare; + } + is1.close(); + is2.close(); + + is1 = jf1.getInputStream(je); + is2 = jf2.getInputStream(je); + + + XMLKit.Element e1 = getXMLelement(is1, ignoreUnkAttrs, ignoreElements); + XMLKit.Element e2 = getXMLelement(is2, ignoreUnkAttrs, ignoreElements); + + Globals.print("+++" + je.getName() + "+++\t" + + e1.size() + "/" + e1.size() + ":"); + + boolean result = true; + + if (e1.equals(e2)) { + Globals.println("PASS"); + } else { + Globals.println("FAIL"); + Globals.log("Strings differs"); + Globals.log(getXMLPrettyString(e1)); + Globals.log("----------"); + Globals.log(getXMLPrettyString(e2)); + result = false; + } + return result; + } + + /* + * Given two Class Paths could be jars the first being a reference + * will execute a series of comparisons on the classname specified + * The className could be null in which case it will iterate through + * all the classes, otherwise it will compare one class and exit. + */ + public static boolean compareClass(String jar1, String jar2, + String className, boolean ignoreUnkAttrs, + List ignoreElements) + throws IOException { + + Globals.println("Unknown attributes ignored:" + ignoreUnkAttrs); + if (ignoreElements != null) { + Globals.println(ignoreElements.toString()); + } + + JarFile jf1 = new JarFile(jar1); + JarFile jf2 = new JarFile(jar2); + + boolean result = true; + + if (className == null) { + for (JarEntry je1 : Collections.list((Enumeration) jf1.entries())) { + if (je1.getName().endsWith(".class")) { + JarEntry je2 = jf2.getJarEntry(je1.getName()); + boolean pf = compareClass0(jf1, jf2, je1, ignoreUnkAttrs, ignoreElements); + if (result == true) { + result = pf; + } + } + } + } else { + JarEntry je1 = jf1.getJarEntry(className); + result = compareClass0(jf1, jf2, je1, ignoreUnkAttrs, ignoreElements); + } + if (result == false) { + throw new RuntimeException("Class structural comparison failure"); + } + return result; + } + + public static boolean compareClass(String jar1, String jar2, + String className) throws IOException { + + Stack s = new Stack(); + if (Globals.ignoreDebugAttributes()) { + s = new Stack(); + s.push("LocalVariable"); + s.push("LocalVariableType"); + s.push("LineNumber"); + s.push("SourceFile"); + } + return compareClass(jar1, jar2, className, Globals.ignoreUnknownAttributes(), s); + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java new file mode 100644 index 00000000000..2a51474bfde --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2010, 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. + */ + +/* + * A collection of useful global utilities commonly used. + */ +package sun.tools.pack.verify; + +import java.io.*; +import java.util.*; + +/* + * @author ksrini + */ + +class Globals { + + private static int errors = 0; + private static PrintWriter _pw = null; + private static String _logFileName = null; + private static final String DEFAULT_LOG_FILE = "verifier.log"; + private static boolean _verbose = true; + private static boolean _ignoreJarDirectories = false; + private static boolean _checkJarClassOrdering = true; + private static boolean _bitWiseClassCompare = false; + // Ignore Deprecated, SourceFile and Synthetic + private static boolean _ignoreCompileAttributes = false; + // Ignore Debug Attributes LocalVariableTable, LocalVariableType,LineNumberTable + private static boolean _ignoreDebugAttributes = false; + private static boolean _ignoreUnknownAttributes = false; + private static boolean _validateClass = true; + private static Globals _instance = null; + + static Globals getInstance() { + if (_instance == null) { + _instance = new Globals(); + _verbose = (System.getProperty("sun.tools.pack.verify.verbose") == null) + ? false : true; + _ignoreJarDirectories = (System.getProperty("ignoreJarDirectories") == null) + ? false : true; + } + return _instance; + } + + static boolean ignoreCompileAttributes() { + return _ignoreCompileAttributes; + } + + static boolean ignoreDebugAttributes() { + return _ignoreDebugAttributes; + } + + static boolean ignoreUnknownAttributes() { + return _ignoreUnknownAttributes; + } + + static boolean ignoreJarDirectories() { + return _ignoreJarDirectories; + } + + static boolean validateClass() { + return _validateClass; + } + + static void setCheckJarClassOrdering(boolean flag) { + _checkJarClassOrdering = flag; + } + + static boolean checkJarClassOrdering() { + return _checkJarClassOrdering; + } + + static boolean bitWiseClassCompare() { + return _bitWiseClassCompare; + } + + static boolean setBitWiseClassCompare(boolean flag) { + return _bitWiseClassCompare = flag; + } + + public static boolean setIgnoreCompileAttributes(boolean flag) { + return _ignoreCompileAttributes = flag; + } + + static boolean setIgnoreDebugAttributes(boolean flag) { + return _ignoreDebugAttributes = flag; + } + + static boolean setIgnoreUnknownAttributes(boolean flag) { + return _ignoreUnknownAttributes = flag; + } + + static boolean setValidateClass(boolean flag) { + return _validateClass = flag; + } + + static int getErrors() { + return errors; + } + + static void trace(String s) { + if (_verbose) { + println(s); + } + } + + static void print(String s) { + _pw.print(s); + } + + static void println(String s) { + _pw.println(s); + } + + static void log(String s) { + errors++; + _pw.println("ERROR:" + s); + } + + static void lognoln(String s) { + errors++; + _pw.print(s); + } + + private static PrintWriter openFile(String fileName) { + //Lets create the directory if it does not exist. + File f = new File(fileName); + File baseDir = f.getParentFile(); + if (baseDir != null && baseDir.exists() == false) { + baseDir.mkdirs(); + } + try { + return new PrintWriter(new FileWriter(f), true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void closeFile() { + _pw.flush(); + _pw.close(); + } + + static void printPropsToLog() { + println("Log started " + new Date(System.currentTimeMillis())); + print(System.getProperty("java.vm.version")); + println("\t" + System.getProperty("java.vm.name")); + + println("System properties"); + println("\tjava.home=" + System.getProperty("java.home")); + println("\tjava.class.version=" + System.getProperty("java.class.version")); + println("\tjava.class.path=" + System.getProperty("java.class.path")); + println("\tjava.ext.dirs=" + System.getProperty("java.ext.dirs")); + println("\tos.name=" + System.getProperty("os.name")); + println("\tos.arch=" + System.getProperty("os.arch")); + println("\tos.version=" + System.getProperty("os.version")); + println("\tuser.name=" + System.getProperty("user.name")); + println("\tuser.home=" + System.getProperty("user.home")); + println("\tuser.dir=" + System.getProperty("user.dir")); + println("\tLocale.getDefault=" + Locale.getDefault()); + println("System properties end"); + } + + static void openLog(String s) { + _logFileName = (s != null) ? s : "." + File.separator + DEFAULT_LOG_FILE; + _logFileName = (new File(_logFileName).isDirectory()) + ? _logFileName + File.separator + DEFAULT_LOG_FILE : _logFileName; + _pw = openFile(_logFileName); + printPropsToLog(); + } + + static void closeLog() { + closeFile(); + } + + static String getLogFileName() { + return _logFileName; + } + + static void diffCharData(String s1, String s2) { + boolean diff = false; + char[] c1 = s1.toCharArray(); + char[] c2 = s2.toCharArray(); + if (c1.length != c2.length) { + diff = true; + Globals.log("Length differs: " + (c1.length - c2.length)); + } + // Take the smaller of the two arrays to prevent Array...Exception + int minlen = (c1.length < c2.length) ? c1.length : c2.length; + for (int i = 0; i < c1.length; i++) { + if (c1[i] != c2[i]) { + diff = true; + Globals.lognoln("\t idx[" + i + "] 0x" + Integer.toHexString(c1[i]) + "<>" + "0x" + Integer.toHexString(c2[i])); + Globals.log(" -> " + c1[i] + "<>" + c2[i]); + } + } + } + + static void diffByteData(String s1, String s2) { + boolean diff = false; + byte[] b1 = s1.getBytes(); + byte[] b2 = s2.getBytes(); + + if (b1.length != b2.length) { + diff = true; + //(+) b1 is greater, (-) b2 is greater + Globals.log("Length differs diff: " + (b1.length - b2.length)); + } + // Take the smaller of the two array to prevent Array...Exception + int minlen = (b1.length < b2.length) ? b1.length : b2.length; + for (int i = 0; i < b1.length; i++) { + if (b1[i] != b2[i]) { + diff = true; + Globals.log("\t" + "idx[" + i + "] 0x" + Integer.toHexString(b1[i]) + "<>" + "0x" + Integer.toHexString(b2[i])); + } + } + } + + static void dumpToHex(String s) { + try { + dumpToHex(s.getBytes("UTF-8")); + } catch (UnsupportedEncodingException uce) { + throw new RuntimeException(uce); + } + } + + static void dumpToHex(byte[] buffer) { + int linecount = 0; + byte[] b = new byte[16]; + for (int i = 0; i < buffer.length; i += 16) { + if (buffer.length - i > 16) { + System.arraycopy(buffer, i, b, 0, 16); + print16Bytes(b, linecount); + linecount += 16; + } else { + System.arraycopy(buffer, i, b, 0, buffer.length - i); + for (int n = buffer.length - (i + 1); n < 16; n++) { + b[n] = 0; + } + print16Bytes(b, linecount); + linecount += 16; + } + } + Globals.log("-----------------------------------------------------------------"); + } + + static void print16Bytes(byte[] buffer, int linecount) { + final int MAX = 4; + Globals.lognoln(paddedHexString(linecount, 4) + " "); + + for (int i = 0; i < buffer.length; i += 2) { + int iOut = pack2Bytes2Int(buffer[i], buffer[i + 1]); + Globals.lognoln(paddedHexString(iOut, 4) + " "); + } + + Globals.lognoln("| "); + + StringBuilder sb = new StringBuilder(new String(buffer)); + + for (int i = 0; i < buffer.length; i++) { + if (Character.isISOControl(sb.charAt(i))) { + sb.setCharAt(i, '.'); + } + } + Globals.log(sb.toString()); + } + + static int pack2Bytes2Int(byte b1, byte b2) { + int out = 0x0; + out += b1; + out <<= 8; + out &= 0x0000ffff; + out |= 0x000000ff & b2; + return out; + } + + static String paddedHexString(int n, int max) { + char[] c = Integer.toHexString(n).toCharArray(); + char[] out = new char[max]; + + for (int i = 0; i < max; i++) { + out[i] = '0'; + } + int offset = (max - c.length < 0) ? 0 : max - c.length; + for (int i = 0; i < c.length; i++) { + out[offset + i] = c[i]; + } + return new String(out); + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java new file mode 100644 index 00000000000..419fda1ba23 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2010, 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. + */ +package sun.tools.pack.verify; + +import java.io.*; +import java.util.*; +import java.util.jar.*; + +class JarFileCompare { + /* + * @author ksrini + */ + + private static VerifyTreeSet getVerifyTreeSet(String jarPath) { + VerifyTreeSet vts = new VerifyTreeSet(); + try { + JarFile j = new JarFile(jarPath); + for (JarEntry je : Collections.list((Enumeration) j.entries())) { + if (!je.isDirectory()) { // totally ignore directories + vts.add(je.getName()); + } + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + return vts; + } + + private static LinkedList getListOfClasses(String jarPath) { + LinkedList l = new LinkedList(); + try { + JarFile j = new JarFile(jarPath); + for (JarEntry je : Collections.list((Enumeration) j.entries())) { + if (!je.isDirectory() && je.getName().endsWith(".class")) { + l.add(je.getName()); + } + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + return l; + } + + private static void jarDirectoryCompare(String jarPath1, String jarPath2) { + VerifyTreeSet vts1 = getVerifyTreeSet(jarPath1); + VerifyTreeSet vts2 = getVerifyTreeSet(jarPath2); + + TreeSet diff1 = vts1.diff(vts2); + if (diff1.size() > 0) { + Globals.log("Left has the following entries that right does not have"); + Globals.log(diff1.toString()); + } + TreeSet diff2 = vts2.diff(vts1); + if (diff2.size() > 0) { + Globals.log("Right has the following entries that left does not have"); + Globals.log(diff2.toString()); + } + if (Globals.checkJarClassOrdering()) { + boolean error = false; + Globals.println("Checking Class Ordering"); + LinkedList l1 = getListOfClasses(jarPath1); + LinkedList l2 = getListOfClasses(jarPath2); + if (l1.size() != l2.size()) { + error = true; + Globals.log("The number of classes differs"); + Globals.log("\t" + l1.size() + "<>" + l2.size()); + } + for (int i = 0; i < l1.size(); i++) { + String s1 = (String) l1.get(i); + String s2 = (String) l2.get(i); + if (s1.compareTo(s2) != 0) { + error = true; + Globals.log("Ordering differs at[" + i + "] = " + s1); + Globals.log("\t" + s2); + } + } + } + } + + /* + * Returns true if the two Streams are bit identical, and false if they + * are not, no further diagnostics + */ + static boolean compareStreams(InputStream is1, InputStream is2) { + + BufferedInputStream bis1 = new BufferedInputStream(is1, 8192); + BufferedInputStream bis2 = new BufferedInputStream(is2, 8192); + try { + int i1, i2; + int count = 0; + while ((i1 = bis1.read()) == (i2 = bis2.read())) { + count++; + if (i1 < 0) { + // System.out.println("bytes read " + count); + return true; // got all the way to EOF + } + } + return false; // reads returned dif + + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static void checkEntry(JarFile jf1, JarFile jf2, JarEntry je) throws IOException { + InputStream is1 = jf1.getInputStream(je); + InputStream is2 = jf2.getInputStream(je); + if (is1 != null && is2 != null) { + if (!compareStreams(jf1.getInputStream(je), jf2.getInputStream(je))) { + Globals.println("+++" + je.getName() + "+++"); + Globals.log("Error: File:" + je.getName() + + " differs, use a diff util for further diagnostics"); + } + } else { + Globals.println("+++" + je.getName() + "+++"); + Globals.log("Error: File:" + je.getName() + " not found in " + jf2.getName()); + } + } + + /* + * Given two jar files we compare and see if the jarfiles have all the + * entries. The property ignoreJarDirectories is set to true by default + * which means that Directory entries in a jar may be ignore. + */ + static void jarCompare(String jarPath1, String jarPath2) { + jarDirectoryCompare(jarPath1, jarPath2); + + try { + JarFile jf1 = new JarFile(jarPath1); + JarFile jf2 = new JarFile(jarPath2); + + int nclasses = 0; + int nentries = 0; + int entries_checked = 0; + int classes_checked = 0; + + for (JarEntry je : Collections.list((Enumeration) jf1.entries())) { + if (!je.isDirectory() && !je.getName().endsWith(".class")) { + nentries++; + } else if (je.getName().endsWith(".class")) { + nclasses++; + } + } + + for (JarEntry je : Collections.list((Enumeration) jf1.entries())) { + if (je.isDirectory()) { + continue; // Ignore directories + } + if (!je.getName().endsWith(".class")) { + entries_checked++; + if (je.getName().compareTo("META-INF/MANIFEST.MF") == 0) { + Manifest mf1 = new Manifest(jf1.getInputStream(je)); + Manifest mf2 = new Manifest(jf2.getInputStream(je)); + if (!mf1.equals(mf2)) { + Globals.log("Error: Manifests differ"); + Globals.log("Manifest1"); + Globals.log(mf1.getMainAttributes().entrySet().toString()); + Globals.log("Manifest2"); + Globals.log(mf2.getMainAttributes().entrySet().toString()); + } + } else { + checkEntry(jf1, jf2, je); + } + } else if (Globals.bitWiseClassCompare() == true) { + checkEntry(jf1, jf2, je); + classes_checked++; + } + } + if (Globals.bitWiseClassCompare()) { + Globals.println("Class entries checked (byte wise)/Total Class entries = " + + classes_checked + "/" + nclasses); + } + Globals.println("Non-class entries checked/Total non-class entries = " + + entries_checked + "/" + nentries); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java new file mode 100644 index 00000000000..481c67b15ce --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2010, 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. + */ +// The Main Entry point +package sun.tools.pack.verify; + +import java.io.*; + +/** + * This class provides a convenient entry point to the pack200 verifier. This + * compares two classes, either in path or in an archive. + * @see xmlkit.XMLKit + * @author ksrini + */ +public class Main { + + private static void syntax() { + System.out.println("Usage: "); + System.out.println("\tREFERENCE_CLASSPATH COMPARED_CLASSPATH [Options]"); + System.out.println("\tOptions:"); + System.out.println("\t\t-O check jar ordering"); + System.out.println("\t\t-C ignore compile attributes (Deprecated, SourceFile, Synthetic, )"); + System.out.println("\t\t-D ignore debug attributes (LocalVariable, LineNumber)"); + System.out.println("\t\t-u ignore unknown attributes"); + System.out.println("\t\t-V turn off class validation"); + System.out.println("\t\t-c CLASS, compare CLASS only"); + System.out.println("\t\t-b Compares all entries bitwise only"); + System.out.println("\t\t-l Directory or Log File Name"); + } + + /** + * main entry point to the class file comparator, which compares semantically + * class files in a classpath or an archive. + * @param args String array as described below + * @throws RuntimeException + *

+     *  Usage:
+     *     ReferenceClasspath SpecimenClaspath [Options]
+     *     Options:
+     *      -O check jar ordering
+     *      -C do not compare compile attributes (Deprecated, SourceFile, Synthetic)
+     *      -D do not compare debug attribute (LocalVariableTable, LineNumberTable)
+     *      -u ignore unknown attributes
+     *      -V turn off class validation
+     *      -c class, compare a single class
+     *      -b compares all entries bitwise (fastest)
+     *      -l directory or log file name
+     * 
+ */ + public static void main(String args[]) { + Globals.getInstance(); + if (args == null || args.length < 2) { + syntax(); + System.exit(1); + } + String refJarFileName = null; + String cmpJarFileName = null; + String specificClass = null; + String logDirFileName = null; + + for (int i = 0; i < args.length; i++) { + if (i == 0) { + refJarFileName = args[0]; + continue; + } + if (i == 1) { + cmpJarFileName = args[1]; + continue; + } + + if (args[i].startsWith("-O")) { + Globals.setCheckJarClassOrdering(true); + } + + if (args[i].startsWith("-b")) { + Globals.setBitWiseClassCompare(true); + } + + if (args[i].startsWith("-C")) { + Globals.setIgnoreCompileAttributes(true); + } + + if (args[i].startsWith("-D")) { + Globals.setIgnoreDebugAttributes(true); + } + + if (args[i].startsWith("-V")) { + Globals.setValidateClass(false); + } + + if (args[i].startsWith("-c")) { + i++; + specificClass = args[i].trim(); + } + + if (args[i].startsWith("-u")) { + i++; + Globals.setIgnoreUnknownAttributes(true); + } + + if (args[i].startsWith("-l")) { + i++; + logDirFileName = args[i].trim(); + } + } + + Globals.openLog(logDirFileName); + + File refJarFile = new File(refJarFileName); + File cmpJarFile = new File(cmpJarFileName); + + String f1 = refJarFile.getAbsoluteFile().toString(); + String f2 = cmpJarFile.getAbsoluteFile().toString(); + + System.out.println("LogFile:" + Globals.getLogFileName()); + System.out.println("Reference JAR:" + f1); + System.out.println("Compared JAR:" + f2); + + Globals.println("LogFile:" + Globals.getLogFileName()); + Globals.println("Reference JAR:" + f1); + Globals.println("Compared JAR:" + f2); + + Globals.println("Ignore Compile Attributes:" + Globals.ignoreCompileAttributes()); + Globals.println("Ignore Debug Attributes:" + Globals.ignoreDebugAttributes()); + Globals.println("Ignore Unknown Attributes:" + Globals.ignoreUnknownAttributes()); + Globals.println("Class ordering check:" + Globals.checkJarClassOrdering()); + Globals.println("Class validation check:" + Globals.validateClass()); + Globals.println("Bit-wise compare:" + Globals.bitWiseClassCompare()); + Globals.println("ClassName:" + ((specificClass == null) ? "ALL" : specificClass)); + + if (specificClass == null && Globals.bitWiseClassCompare() == true) { + JarFileCompare.jarCompare(refJarFileName, cmpJarFileName); + } else { + try { + ClassCompare.compareClass(refJarFileName, cmpJarFileName, specificClass); + } catch (Exception e) { + Globals.log("Exception " + e); + throw new RuntimeException(e); + } + } + + if (Globals.getErrors() > 0) { + System.out.println("FAIL"); + Globals.println("FAIL"); + System.exit(Globals.getErrors()); + } + + System.out.println("PASS"); + Globals.println("PASS"); + System.exit(Globals.getErrors()); + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java new file mode 100644 index 00000000000..1e4c32b2af0 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010, 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. + */ + +package sun.tools.pack.verify; + +import java.util.*; +/* + * @author ksrini + */ + +class VerifyTreeSet extends java.util.TreeSet { + + VerifyTreeSet() { + super(); + } + + public VerifyTreeSet(Comparator c) { + super(c); + } + + public TreeSet diff(TreeSet in) { + TreeSet delta = (TreeSet) this.clone(); + delta.removeAll(in); + return delta; + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java new file mode 100644 index 00000000000..4a3af66344e --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java @@ -0,0 +1,1003 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- + +import java.util.*; +import java.util.jar.*; +import java.lang.reflect.*; +import java.io.*; +import xmlkit.XMLKit.Element; + +/* + * @author jrose + */ +public class ClassReader extends ClassSyntax { + + private static final CommandLineParser CLP = new CommandLineParser("" + + "-source: +> = \n" + + "-dest: +> = \n" + + "-encoding: +> = \n" + + "-jcov $ \n -nojcov !-jcov \n" + + "-verbose $ \n -noverbose !-verbose \n" + + "-pretty $ \n -nopretty !-pretty \n" + + "-keepPath $ \n -nokeepPath !-keepPath \n" + + "-keepCP $ \n -nokeepCP !-keepCP \n" + + "-keepBytes $ \n -nokeepBytes !-keepBytes \n" + + "-parseBytes $ \n -noparseBytes !-parseBytes \n" + + "-resolveRefs $ \n -noresolveRefs !-resolveRefs \n" + + "-keepOrder $ \n -nokeepOrder !-keepOrder \n" + + "-keepSizes $ \n -nokeepSizes !-keepSizes \n" + + "-continue $ \n -nocontinue !-continue \n" + + "-attrDef & \n" + + "-@ >-@ . \n" + + "- +? \n" + + "\n"); + + public static void main(String[] ava) throws IOException { + ArrayList av = new ArrayList(Arrays.asList(ava)); + HashMap props = new HashMap(); + props.put("-encoding:", "UTF8"); // default + props.put("-keepOrder", null); // CLI default + props.put("-pretty", "1"); // CLI default + props.put("-continue", "1"); // CLI default + CLP.parse(av, props); + //System.out.println(props+" ++ "+av); + File source = asFile(props.get("-source:")); + File dest = asFile(props.get("-dest:")); + String encoding = props.get("-encoding:"); + boolean contError = props.containsKey("-continue"); + ClassReader options = new ClassReader(); + options.copyOptionsFrom(props); + /* + if (dest == null && av.size() > 1) { + dest = File.createTempFile("TestOut", ".dir", new File(".")); + dest.delete(); + if (!dest.mkdir()) + throw new RuntimeException("Cannot create "+dest); + System.out.println("Writing results to "+dest); + } + */ + if (av.isEmpty()) { + av.add("doit"); //to enter this loop + } + boolean readList = false; + for (String a : av) { + if (readList) { + readList = false; + InputStream fin; + if (a.equals("-")) { + fin = System.in; + } else { + fin = new FileInputStream(a); + } + + BufferedReader files = makeReader(fin, encoding); + for (String file; (file = files.readLine()) != null;) { + doFile(file, source, dest, options, encoding, contError); + } + if (fin != System.in) { + fin.close(); + } + } else if (a.equals("-@")) { + readList = true; + } else if (a.startsWith("-")) { + throw new RuntimeException("Bad flag argument: " + a); + } else if (source.getName().endsWith(".jar")) { + doJar(a, source, dest, options, encoding, contError); + } else { + doFile(a, source, dest, options, encoding, contError); + } + } + } + + private static File asFile(String str) { + return (str == null) ? null : new File(str); + } + + private static void doFile(String a, + File source, File dest, + ClassReader options, String encoding, + boolean contError) throws IOException { + if (!contError) { + doFile(a, source, dest, options, encoding); + } else { + try { + doFile(a, source, dest, options, encoding); + } catch (Exception ee) { + System.out.println("Error processing " + source + ": " + ee); + } + } + } + + private static void doJar(String a, File source, File dest, ClassReader options, + String encoding, Boolean contError) throws IOException { + try { + JarFile jf = new JarFile(source); + for (JarEntry je : Collections.list((Enumeration) jf.entries())) { + String name = je.getName(); + if (!name.endsWith(".class")) { + continue; + } + doStream(name, jf.getInputStream(je), dest, options, encoding); + } + } catch (IOException ioe) { + if (contError) { + System.out.println("Error processing " + source + ": " + ioe); + } else { + throw ioe; + } + } + } + + private static void doStream(String a, InputStream in, File dest, + ClassReader options, String encoding) throws IOException { + + File f = new File(a); + ClassReader cr = new ClassReader(options); + Element e = cr.readFrom(in); + + OutputStream out; + if (dest == null) { + //System.out.println(e.prettyString()); + out = System.out; + } else { + File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath()); + String outName = outf.getName(); + File outSubdir = outf.getParentFile(); + outSubdir.mkdirs(); + int extPos = outName.lastIndexOf('.'); + if (extPos > 0) { + outf = new File(outSubdir, outName.substring(0, extPos) + ".xml"); + } + out = new FileOutputStream(outf); + } + + Writer outw = makeWriter(out, encoding); + if (options.pretty || !options.keepOrder) { + e.writePrettyTo(outw); + } else { + e.writeTo(outw); + } + if (out == System.out) { + outw.write("\n"); + outw.flush(); + } else { + outw.close(); + } + } + + private static void doFile(String a, + File source, File dest, + ClassReader options, String encoding) throws IOException { + File inf = new File(source, a); + if (dest != null && options.verbose) { + System.out.println("Reading " + inf); + } + + BufferedInputStream in = new BufferedInputStream(new FileInputStream(inf)); + + doStream(a, in, dest, options, encoding); + + } + + public static BufferedReader makeReader(InputStream in, String encoding) throws IOException { + // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name + if (encoding.equals("8BIT")) { + encoding = EIGHT_BIT_CHAR_ENCODING; + } + if (encoding.equals("UTF8")) { + encoding = UTF8_ENCODING; + } + if (encoding.equals("DEFAULT")) { + encoding = null; + } + if (encoding.equals("-")) { + encoding = null; + } + Reader inw; + in = new BufferedInputStream(in); // add buffering + if (encoding == null) { + inw = new InputStreamReader(in); + } else { + inw = new InputStreamReader(in, encoding); + } + return new BufferedReader(inw); // add buffering + } + + public static Writer makeWriter(OutputStream out, String encoding) throws IOException { + // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name + if (encoding.equals("8BIT")) { + encoding = EIGHT_BIT_CHAR_ENCODING; + } + if (encoding.equals("UTF8")) { + encoding = UTF8_ENCODING; + } + if (encoding.equals("DEFAULT")) { + encoding = null; + } + if (encoding.equals("-")) { + encoding = null; + } + Writer outw; + if (encoding == null) { + outw = new OutputStreamWriter(out); + } else { + outw = new OutputStreamWriter(out, encoding); + } + return new BufferedWriter(outw); // add buffering + } + + public Element result() { + return cfile; + } + protected InputStream in; + protected ByteArrayOutputStream buf = new ByteArrayOutputStream(1024); + protected byte cpTag[]; + protected String cpName[]; + protected String[] callables; // varies + public static final String REF_PREFIX = "#"; + // input options + public boolean pretty = false; + public boolean verbose = false; + public boolean keepPath = false; + public boolean keepCP = false; + public boolean keepBytes = false; + public boolean parseBytes = true; + public boolean resolveRefs = true; + public boolean keepOrder = true; + public boolean keepSizes = false; + + public ClassReader() { + super.cfile = new Element("ClassFile"); + } + + public ClassReader(ClassReader options) { + this(); + copyOptionsFrom(options); + } + + public void copyOptionsFrom(ClassReader options) { + pretty = options.pretty; + verbose = options.verbose; + keepPath = options.keepPath; + keepCP = options.keepCP; + keepBytes = options.keepBytes; + parseBytes = options.parseBytes; + resolveRefs = options.resolveRefs; + keepSizes = options.keepSizes; + keepOrder = options.keepOrder; + attrTypes = options.attrTypes; + } + + public void copyOptionsFrom(Map options) { + if (options.containsKey("-pretty")) { + pretty = (options.get("-pretty") != null); + } + if (options.containsKey("-verbose")) { + verbose = (options.get("-verbose") != null); + } + if (options.containsKey("-keepPath")) { + keepPath = (options.get("-keepPath") != null); + } + if (options.containsKey("-keepCP")) { + keepCP = (options.get("-keepCP") != null); + } + if (options.containsKey("-keepBytes")) { + keepBytes = (options.get("-keepBytes") != null); + } + if (options.containsKey("-parseBytes")) { + parseBytes = (options.get("-parseBytes") != null); + } + if (options.containsKey("-resolveRefs")) { + resolveRefs = (options.get("-resolveRefs") != null); + } + if (options.containsKey("-keepSizes")) { + keepSizes = (options.get("-keepSizes") != null); + } + if (options.containsKey("-keepOrder")) { + keepOrder = (options.get("-keepOrder") != null); + } + if (options.containsKey("-attrDef")) { + addAttrTypes(options.get("-attrDef").split(" ")); + } + if (options.get("-jcov") != null) { + addJcovAttrTypes(); + } + } + + public Element readFrom(InputStream in) throws IOException { + this.in = in; + // read the file header + int magic = u4(); + if (magic != 0xCAFEBABE) { + throw new RuntimeException("bad magic number " + Integer.toHexString(magic)); + } + cfile.setAttr("magic", "" + magic); + int minver = u2(); + int majver = u2(); + cfile.setAttr("minver", "" + minver); + cfile.setAttr("majver", "" + majver); + readCP(); + readClass(); + return result(); + } + + public Element readFrom(File file) throws IOException { + InputStream in = null; + try { + in = new FileInputStream(file); + Element e = readFrom(new BufferedInputStream(in)); + if (keepPath) { + e.setAttr("path", file.toString()); + } + return e; + } finally { + if (in != null) { + in.close(); + } + } + } + + private void readClass() throws IOException { + klass = new Element("Class"); + cfile.add(klass); + int flags = u2(); + String thisk = cpRef(); + String superk = cpRef(); + klass.setAttr("name", thisk); + boolean flagsSync = ((flags & Modifier.SYNCHRONIZED) != 0); + flags &= ~Modifier.SYNCHRONIZED; + String flagString = flagString(flags, klass); + if (!flagsSync) { + if (flagString.length() > 0) { + flagString += " "; + } + flagString += "!synchronized"; + } + klass.setAttr("flags", flagString); + klass.setAttr("super", superk); + for (int len = u2(), i = 0; i < len; i++) { + String interk = cpRef(); + klass.add(new Element("Interface", "name", interk)); + } + Element fields = readMembers("Field"); + klass.addAll(fields); + Element methods = readMembers("Method"); + if (!keepOrder) { + methods.sort(); + } + klass.addAll(methods); + readAttributesFor(klass); + klass.trimToSize(); + if (keepSizes) { + attachTo(cfile, formatAttrSizes()); + } + if (paddingSize != 0) { + cfile.setAttr("padding", "" + paddingSize); + } + } + + private Element readMembers(String kind) throws IOException { + int len = u2(); + Element members = new Element(len); + for (int i = 0; i < len; i++) { + Element member = new Element(kind); + int flags = u2(); + String name = cpRef(); + String type = cpRef(); + member.setAttr("name", name); + member.setAttr("type", type); + member.setAttr("flags", flagString(flags, member)); + readAttributesFor(member); + member.trimToSize(); + members.add(member); + } + return members; + } + + protected String flagString(int flags, Element holder) { + // Superset of Modifier.toString. + int kind = 0; + if (holder.getName() == "Field") { + kind = 1; + } + if (holder.getName() == "Method") { + kind = 2; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; flags != 0; i++, flags >>>= 1) { + if ((flags & 1) != 0) { + if (sb.length() > 0) { + sb.append(' '); + } + if (i < modifierNames.length) { + String[] names = modifierNames[i]; + String name = (kind < names.length) ? names[kind] : null; + for (String name2 : names) { + if (name != null) { + break; + } + name = name2; + } + sb.append(name); + } else { + sb.append("#").append(1 << i); + } + } + } + return sb.toString(); + } + + private void readAttributesFor(Element x) throws IOException { + Element prevCurrent; + Element y = new Element(); + if (x.getName() == "Code") { + prevCurrent = currentCode; + currentCode = x; + } else { + prevCurrent = currentMember; + currentMember = x; + } + for (int len = u2(), i = 0; i < len; i++) { + int ref = u2(); + String uname = cpName(ref).intern(); + String refName = uname; + if (!resolveRefs) { + refName = (REF_PREFIX + ref).intern(); + } + String qname = (x.getName() + "." + uname).intern(); + String wname = ("*." + uname).intern(); + String type = attrTypes.get(qname); + if (type == null || "".equals(type)) { + type = attrTypes.get(wname); + } + if ("".equals(type)) { + type = null; + } + int size = u4(); + int[] countVar = attrSizes.get(qname); + if (countVar == null) { + attrSizes.put(qname, countVar = new int[2]); + } + countVar[0] += 1; + countVar[1] += size; + buf.reset(); + for (int j = 0; j < size; j++) { + buf.write(u1()); + } + if (type == null && size == 0) { + y.add(new Element(uname)); // , etc. + } else if (type == null) { + //System.out.println("Warning: No attribute type description: "+qname); + // write cdata attribute + Element a = new Element("Attribute", + new String[]{"Name", refName}, + buf.toString(EIGHT_BIT_CHAR_ENCODING)); + a.addContent(getCPDigest()); + y.add(a); + } else if (type.equals("")) { + // ignore this attribute... + } else { + InputStream in0 = in; + int fileSize0 = fileSize; + ByteArrayInputStream in1 = new ByteArrayInputStream(buf.toByteArray()); + boolean ok = false; + try { + in = in1; + // parse according to type desc. + Element aval; + if (type.equals("...")) { + // delve into Code attribute + aval = readCode(); + } else if (type.equals("...")) { + // delve into StackMap attribute + aval = readStackMap(false); + } else if (type.equals("...")) { + // delve into StackMap attribute + aval = readStackMap(true); + } else if (type.startsWith("[")) { + aval = readAttributeCallables(type); + } else { + aval = readAttribute(type); + } + //System.out.println("attachTo 1 "+y+" <- "+aval); + attachTo(y, aval); + if (false + && in1.available() != 0) { + throw new RuntimeException("extra bytes in " + qname + " :" + in1.available()); + } + ok = true; + } finally { + in = in0; + fileSize = fileSize0; + if (!ok) { + System.out.println("*** Failed to read " + type); + } + } + } + } + if (x.getName() == "Code") { + currentCode = prevCurrent; + } else { + currentMember = prevCurrent; + } + if (!keepOrder) { + y.sort(); + y.sortAttrs(); + } + //System.out.println("attachTo 2 "+x+" <- "+y); + attachTo(x, y); + } + private int fileSize = 0; + private int paddingSize = 0; + private HashMap attrSizes = new HashMap(); + + private Element formatAttrSizes() { + Element e = new Element("Sizes"); + e.setAttr("fileSize", "" + fileSize); + for (Map.Entry ie : attrSizes.entrySet()) { + int[] countVar = ie.getValue(); + e.add(new Element("AttrSize", + "name", ie.getKey().toString(), + "count", "" + countVar[0], + "size", "" + countVar[1])); + } + return e; + } + + private void attachTo(Element x, Object aval0) { + if (aval0 == null) { + return; + } + //System.out.println("attachTo "+x+" : "+aval0); + if (!(aval0 instanceof Element)) { + x.add(aval0); + return; + } + Element aval = (Element) aval0; + if (!aval.isAnonymous()) { + x.add(aval); + return; + } + for (int imax = aval.attrSize(), i = 0; i < imax; i++) { + //%% + attachAttrTo(x, aval.getAttrName(i), aval.getAttr(i)); + } + x.addAll(aval); + } + + private void attachAttrTo(Element x, String aname, String aval) { + //System.out.println("attachAttrTo "+x+" : "+aname+"="+aval); + String aval0 = x.getAttr(aname); + if (aval0 != null) { + aval = aval0 + " " + aval; + } + x.setAttr(aname, aval); + } + + private Element readAttributeCallables(String type) throws IOException { + assert (callables == null); + callables = getBodies(type); + Element res = readAttribute(callables[0]); + callables = null; + return res; + } + + private Element readAttribute(String type) throws IOException { + //System.out.println("readAttribute "+type); + Element aval = new Element(); + String nextAttrName = null; + for (int len = type.length(), next, i = 0; i < len; i = next) { + String value; + switch (type.charAt(i)) { + case '<': + assert (nextAttrName == null); + next = type.indexOf('>', ++i); + String form = type.substring(i, next++); + if (form.indexOf('=') < 0) { + // elem_placement = '<' elemname '>' + assert (aval.attrSize() == 0); + assert (aval.isAnonymous()); + aval.setName(form.intern()); + } else { + // attr_placement = '<' attrname '=' (value)? '>' + int eqPos = form.indexOf('='); + nextAttrName = form.substring(0, eqPos).intern(); + if (eqPos != form.length() - 1) { + value = form.substring(eqPos + 1); + attachAttrTo(aval, nextAttrName, value); + nextAttrName = null; + } + // ...else subsequent type parsing will find the attr value + // and add it as "nextAttrName". + } + continue; + case '(': + next = type.indexOf(')', ++i); + int callee = Integer.parseInt(type.substring(i, next++)); + attachTo(aval, readAttribute(callables[callee])); + continue; + case 'N': // replication = 'N' int '[' type ... ']' + { + int count = getInt(type.charAt(i + 1), false); + assert (count >= 0); + next = i + 2; + String type1 = getBody(type, next); + next += type1.length() + 2; // skip body and brackets + for (int j = 0; j < count; j++) { + attachTo(aval, readAttribute(type1)); + } + } + continue; + case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']' + int tagValue; + if (type.charAt(++i) == 'S') { + tagValue = getInt(type.charAt(++i), true); + } else { + tagValue = getInt(type.charAt(i), false); + } + attachAttrTo(aval, "tag", "" + tagValue); // always named "tag" + ++i; // skip the int type char + // union_case = '(' uc_tag (',' uc_tag)* ')' '[' body ']' + // uc_tag = ('-')? digit+ + for (boolean foundCase = false;; i = next) { + assert (type.charAt(i) == '('); + next = type.indexOf(')', ++i); + assert (next >= i); + if (type.charAt(next - 1) == '\\' + && type.charAt(next - 2) != '\\') // Skip an escaped paren. + { + next = type.indexOf(')', next + 1); + } + String caseStr = type.substring(i, next++); + String type1 = getBody(type, next); + next += type1.length() + 2; // skip body and brackets + boolean lastCase = (caseStr.length() == 0); + if (!foundCase + && (lastCase || matchTag(tagValue, caseStr))) { + foundCase = true; + // Execute this body. + attachTo(aval, readAttribute(type1)); + } + if (lastCase) { + break; + } + } + continue; + case 'B': + case 'H': + case 'I': // int = oneof "BHI" + next = i + 1; + value = "" + getInt(type.charAt(i), false); + break; + case 'K': + assert ("IJFDLQ".indexOf(type.charAt(i + 1)) >= 0); + assert (type.charAt(i + 2) == 'H'); // only H works for now + next = i + 3; + value = cpRef(); + break; + case 'R': + assert ("CSDFMIU?".indexOf(type.charAt(i + 1)) >= 0); + assert (type.charAt(i + 2) == 'H'); // only H works for now + next = i + 3; + value = cpRef(); + break; + case 'P': // bci = 'P' int + next = i + 2; + value = "" + getInt(type.charAt(i + 1), false); + break; + case 'S': // signed_int = 'S' int + next = i + 2; + value = "" + getInt(type.charAt(i + 1), true); + break; + case 'F': + next = i + 2; + value = flagString(getInt(type.charAt(i + 1), false), currentMember); + break; + default: + throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type); + } + // store the value + if (nextAttrName != null) { + attachAttrTo(aval, nextAttrName, value); + nextAttrName = null; + } else { + attachTo(aval, value); + } + } + //System.out.println("readAttribute => "+aval); + assert (nextAttrName == null); + return aval; + } + + private int getInt(char ch, boolean signed) throws IOException { + if (signed) { + switch (ch) { + case 'B': + return (byte) u1(); + case 'H': + return (short) u2(); + case 'I': + return (int) u4(); + } + } else { + switch (ch) { + case 'B': + return u1(); + case 'H': + return u2(); + case 'I': + return u4(); + } + } + assert ("BHIJ".indexOf(ch) >= 0); + return 0; + } + + private Element readCode() throws IOException { + int stack = u2(); + int local = u2(); + int length = u4(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append((char) u1()); + } + String bytecodes = sb.toString(); + Element e = new Element("Code", + "stack", "" + stack, + "local", "" + local); + Element bytes = new Element("Bytes", (String[]) null, bytecodes); + if (keepBytes) { + e.add(bytes); + } + if (parseBytes) { + e.add(parseByteCodes(bytecodes)); + } + for (int len = u2(), i = 0; i < len; i++) { + int start = u2(); + int end = u2(); + int catsh = u2(); + String clasz = cpRef(); + e.add(new Element("Handler", + "start", "" + start, + "end", "" + end, + "catch", "" + catsh, + "class", clasz)); + } + readAttributesFor(e); + e.trimToSize(); + return e; + } + + private Element parseByteCodes(String bytecodes) { + Element e = InstructionSyntax.parse(bytecodes); + for (Element ins : e.elements()) { + Number ref = ins.getAttrNumber("ref"); + if (ref != null && resolveRefs) { + int id = ref.intValue(); + String val = cpName(id); + if (ins.getName().startsWith("ldc")) { + // Yuck: Arb. string cannot be an XML attribute. + ins.add(val); + val = ""; + byte tag = (id >= 0 && id < cpTag.length) ? cpTag[id] : 0; + if (tag != 0) { + ins.setAttrLong("tag", tag); + } + } + if (ins.getName() == "invokeinterface" + && computeInterfaceNum(val) == ins.getAttrLong("num")) { + ins.setAttr("num", null); // garbage bytes + } + ins.setAttr("ref", null); + ins.setAttr("val", val); + } + } + return e; + } + + private Element readStackMap(boolean hasXOption) throws IOException { + Element result = new Element(); + Element bytes = currentCode.findElement("Bytes"); + assert (bytes != null && bytes.size() == 1); + int byteLength = ((String) bytes.get(0)).length(); + boolean uoffsetIsU4 = (byteLength >= (1 << 16)); + boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16); + boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16); + if (hasXOption || uoffsetIsU4 || ulocalvarIsU4 || ustackIsU4) { + Element flags = new Element("StackMapFlags"); + if (hasXOption) { + flags.setAttr("hasXOption", "true"); + } + if (uoffsetIsU4) { + flags.setAttr("uoffsetIsU4", "true"); + } + if (ulocalvarIsU4) { + flags.setAttr("ulocalvarIsU4", "true"); + } + if (ustackIsU4) { + flags.setAttr("ustackIsU4", "true"); + } + currentCode.add(flags); + } + int frame_count = (uoffsetIsU4 ? u4() : u2()); + for (int i = 0; i < frame_count; i++) { + int bci = (uoffsetIsU4 ? u4() : u2()); + int flags = (hasXOption ? u1() : 0); + Element frame = new Element("Frame"); + result.add(frame); + if (flags != 0) { + frame.setAttr("flags", "" + flags); + } + frame.setAttr("bci", "" + bci); + // Scan local and stack types in this frame: + final int LOCALS = 0, STACK = 1; + for (int j = LOCALS; j <= STACK; j++) { + int typeSize; + if (j == LOCALS) { + typeSize = (ulocalvarIsU4 ? u4() : u2()); + } else { // STACK + typeSize = (ustackIsU4 ? u4() : u2()); + } + Element types = new Element(j == LOCALS ? "Local" : "Stack"); + for (int k = 0; k < typeSize; k++) { + int tag = u1(); + Element type = new Element(itemTagName(tag)); + types.add(type); + switch (tag) { + case ITEM_Object: + type.setAttr("class", cpRef()); + break; + case ITEM_Uninitialized: + case ITEM_ReturnAddress: + type.setAttr("bci", "" + (uoffsetIsU4 ? u4() : u2())); + break; + } + } + if (types.size() > 0) { + frame.add(types); + } + } + } + return result; + } + + private void readCP() throws IOException { + int cpLen = u2(); + cpTag = new byte[cpLen]; + cpName = new String[cpLen]; + int cpTem[][] = new int[cpLen][]; + for (int i = 1; i < cpLen; i++) { + cpTag[i] = (byte) u1(); + switch (cpTag[i]) { + case CONSTANT_Utf8: + buf.reset(); + for (int len = u2(), j = 0; j < len; j++) { + buf.write(u1()); + } + cpName[i] = buf.toString(UTF8_ENCODING); + break; + case CONSTANT_Integer: + cpName[i] = String.valueOf((int) u4()); + break; + case CONSTANT_Float: + cpName[i] = String.valueOf(Float.intBitsToFloat(u4())); + break; + case CONSTANT_Long: + cpName[i] = String.valueOf(u8()); + i += 1; + break; + case CONSTANT_Double: + cpName[i] = String.valueOf(Double.longBitsToDouble(u8())); + i += 1; + break; + case CONSTANT_Class: + case CONSTANT_String: + cpTem[i] = new int[]{u2()}; + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameAndType: + cpTem[i] = new int[]{u2(), u2()}; + break; + } + } + for (int i = 1; i < cpLen; i++) { + switch (cpTag[i]) { + case CONSTANT_Class: + case CONSTANT_String: + cpName[i] = cpName[cpTem[i][0]]; + break; + case CONSTANT_NameAndType: + cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]]; + break; + } + } + // do fieldref et al after nameandtype are all resolved + for (int i = 1; i < cpLen; i++) { + switch (cpTag[i]) { + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]]; + break; + } + } + cpool = new Element("ConstantPool", cpName.length); + for (int i = 0; i < cpName.length; i++) { + if (cpName[i] == null) { + continue; + } + cpool.add(new Element(cpTagName(cpTag[i]), + new String[]{"id", "" + i}, + cpName[i])); + } + if (keepCP) { + cfile.add(cpool); + } + } + + private String cpRef() throws IOException { + int ref = u2(); + if (resolveRefs) { + return cpName(ref); + } else { + return REF_PREFIX + ref; + } + } + + private String cpName(int id) { + if (id >= 0 && id < cpName.length) { + return cpName[id]; + } else { + return "[CP#" + Integer.toHexString(id) + "]"; + } + } + + private long u8() throws IOException { + return ((long) u4() << 32) + (((long) u4() << 32) >>> 32); + } + + private int u4() throws IOException { + return (u2() << 16) + u2(); + } + + private int u2() throws IOException { + return (u1() << 8) + u1(); + } + + private int u1() throws IOException { + int x = in.read(); + if (x < 0) { + paddingSize++; + return 0; // error recovery + } + fileSize++; + assert (x == (x & 0xFF)); + return x; + } +} + diff --git a/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java new file mode 100644 index 00000000000..d34ecbad004 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- +import xmlkit.XMLKit.*; + +import java.util.*; +import java.security.MessageDigest; +import java.nio.ByteBuffer; +import xmlkit.XMLKit.Element; +/* + * @author jrose + */ +public abstract class ClassSyntax { + + public interface GetCPIndex { + + int getCPIndex(int tag, String name); // cp finder + } + public static final int CONSTANT_Utf8 = 1, + CONSTANT_Integer = 3, + CONSTANT_Float = 4, + CONSTANT_Long = 5, + CONSTANT_Double = 6, + CONSTANT_Class = 7, + CONSTANT_String = 8, + CONSTANT_Fieldref = 9, + CONSTANT_Methodref = 10, + CONSTANT_InterfaceMethodref = 11, + CONSTANT_NameAndType = 12; + private static final String[] cpTagName = { + /* 0: */null, + /* 1: */ "Utf8", + /* 2: */ null, + /* 3: */ "Integer", + /* 4: */ "Float", + /* 5: */ "Long", + /* 6: */ "Double", + /* 7: */ "Class", + /* 8: */ "String", + /* 9: */ "Fieldref", + /* 10: */ "Methodref", + /* 11: */ "InterfaceMethodref", + /* 12: */ "NameAndType", + null + }; + private static final Set cpTagNames; + + static { + Set set = new HashSet(Arrays.asList(cpTagName)); + set.remove(null); + cpTagNames = Collections.unmodifiableSet(set); + } + public static final int ITEM_Top = 0, // replicates by [1..4,1..4] + ITEM_Integer = 1, // (ditto) + ITEM_Float = 2, + ITEM_Double = 3, + ITEM_Long = 4, + ITEM_Null = 5, + ITEM_UninitializedThis = 6, + ITEM_Object = 7, + ITEM_Uninitialized = 8, + ITEM_ReturnAddress = 9, + ITEM_LIMIT = 10; + private static final String[] itemTagName = { + "Top", + "Integer", + "Float", + "Double", + "Long", + "Null", + "UninitializedThis", + "Object", + "Uninitialized", + "ReturnAddress",}; + private static final Set itemTagNames; + + static { + Set set = new HashSet(Arrays.asList(itemTagName)); + set.remove(null); + itemTagNames = Collections.unmodifiableSet(set); + } + protected static final HashMap attrTypesBacking; + protected static final Map attrTypesInit; + + static { + HashMap at = new HashMap(); + + //at.put("*.Deprecated", ""); + //at.put("*.Synthetic", ""); + ////at.put("Field.ConstantValue", "KQH"); + //at.put("Class.SourceFile", "RUH"); + at.put("Method.Bridge", ""); + at.put("Method.Varargs", ""); + at.put("Class.Enum", ""); + at.put("*.Signature", "RSH"); + //at.put("*.Deprecated", ""); + //at.put("*.Synthetic", ""); + at.put("Field.ConstantValue", "KQH"); + at.put("Class.SourceFile", "RUH"); + at.put("Class.InnerClasses", "NH[RCHRCHRUHFH]"); + at.put("Code.LineNumberTable", "NH[PHH]"); + at.put("Code.LocalVariableTable", "NH[PHHRUHRSHH]"); + at.put("Code.LocalVariableTypeTable", "NH[PHHRUHRSHH]"); + at.put("Method.Exceptions", "NH[RCH]"); + at.put("Method.Code", "..."); + at.put("Code.StackMapTable", "..."); + //at.put("Code.StkMapX", "..."); + if (true) { + at.put("Code.StackMapTable", + "[NH[(1)]]" + + "[TB" + + "(64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79" + + ",80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95" + + ",96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111" + + ",112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127" + + ")[(4)]" + + "(247)[H(4)]" + + "(248)[H]" + + "(249)[H]" + + "(250)[H]" + + "(251)[H]" + + "(252)[H(4)]" + + "(253)[H(4)(4)]" + + "(254)[H(4)(4)(4)]" + + "(255)[H(2)(3)]" + + "()[]]" + + "[NH[(4)]]" + + "[NH[(4)]]" + + "[TB" + + ("(0)[]" + + "(1)[](2)[](3)[](4)[]" + + "(5)[](6)[]" + + "(7)[RCH]" + + "(8)[PH]" + + "()[]]")); + } + + at.put("Class.EnclosingMethod", "RCHRDH");//RDNH + + // Layouts of metadata attrs: + String vpf = "["; + String ipf = "["; + String apf = "["; + String mdanno2 = "" + + "RSHNH[RUH(3)]]" + + ("[TB" + + "(\\B,\\C,\\I,\\S,\\Z)[KIH]" + + "(\\D)[KDH]" + + "(\\F)[KFH]" + + "(\\J)[KJH]" + + "(\\c)[RSH]" + + "(\\e)[RSHRUH]" + + "(\\s)[RUH]" + + "(\\@)[(2)]" + + "(\\[)[NH[(3)]]" + + "()[]" + + "]"); + String visanno = "[NH[(2)]][(1)]" + vpf + mdanno2; + String invanno = "[NH[(2)]][(1)]" + ipf + mdanno2; + String vparamanno = "" + + "[NB[(1)]][NH[(2)]]" + + apf + mdanno2; + String iparamanno = "" + + "[NB[(1)]][NH[(2)]]" + + apf + mdanno2; + String mdannodef = "[(3)][(1)]" + apf + mdanno2; + String[] mdplaces = {"Class", "Field", "Method"}; + for (String place : mdplaces) { + at.put(place + ".RuntimeVisibleAnnotations", visanno); + at.put(place + ".RuntimeInvisibleAnnotations", invanno); + } + at.put("Method.RuntimeVisibleParameterAnnotations", vparamanno); + at.put("Method.RuntimeInvisibleParameterAnnotations", iparamanno); + at.put("Method.AnnotationDefault", mdannodef); + + attrTypesBacking = at; + attrTypesInit = Collections.unmodifiableMap(at); + } + + ; + private static final String[] jcovAttrTypes = { + "Code.CoverageTable=NH[PHHII]", + "Code.CharacterRangeTable=NH[PHPOHIIH]", + "Class.SourceID=RUH", + "Class.CompilationID=RUH" + }; + protected static final String[][] modifierNames = { + {"public"}, + {"private"}, + {"protected"}, + {"static"}, + {"final"}, + {"synchronized"}, + {null, "volatile", "bridge"}, + {null, "transient", "varargs"}, + {null, null, "native"}, + {"interface"}, + {"abstract"}, + {"strictfp"}, + {"synthetic"}, + {"annotation"}, + {"enum"},}; + protected static final String EIGHT_BIT_CHAR_ENCODING = "ISO8859_1"; + protected static final String UTF8_ENCODING = "UTF8"; + // What XML tags are used by this syntax, apart from attributes? + protected static final Set nonAttrTags; + + static { + HashSet tagSet = new HashSet(); + Collections.addAll(tagSet, new String[]{ + "ConstantPool",// the CP + "Class", // the class + "Interface", // implemented interfaces + "Method", // methods + "Field", // fields + "Handler", // exception handler pseudo-attribute + "Attribute", // unparsed attribute + "Bytes", // bytecodes + "Instructions" // bytecodes, parsed + }); + nonAttrTags = Collections.unmodifiableSet(tagSet); + } + + // Accessors. + public static Set nonAttrTags() { + return nonAttrTags; + } + + public static String cpTagName(int t) { + t &= 0xFF; + String ts = null; + if (t < cpTagName.length) { + ts = cpTagName[t]; + } + if (ts != null) { + return ts; + } + return ("UnknownTag" + (int) t).intern(); + } + + public static int cpTagValue(String name) { + for (int t = 0; t < cpTagName.length; t++) { + if (name.equals(cpTagName[t])) { + return t; + } + } + return 0; + } + + public static String itemTagName(int t) { + t &= 0xFF; + String ts = null; + if (t < itemTagName.length) { + ts = itemTagName[t]; + } + if (ts != null) { + return ts; + } + return ("UnknownItem" + (int) t).intern(); + } + + public static int itemTagValue(String name) { + for (int t = 0; t < itemTagName.length; t++) { + if (name.equals(itemTagName[t])) { + return t; + } + } + return -1; + } + + public void addJcovAttrTypes() { + addAttrTypes(jcovAttrTypes); + } + // Public methods for declaring attribute types. + protected Map attrTypes = attrTypesInit; + + public void addAttrType(String opt) { + int eqpos = opt.indexOf('='); + addAttrType(opt.substring(0, eqpos), opt.substring(eqpos + 1)); + } + + public void addAttrTypes(String[] opts) { + for (String opt : opts) { + addAttrType(opt); + } + } + + private void checkAttr(String attr) { + if (!attr.startsWith("Class.") + && !attr.startsWith("Field.") + && !attr.startsWith("Method.") + && !attr.startsWith("Code.") + && !attr.startsWith("*.")) { + throw new IllegalArgumentException("attr name must start with 'Class.', etc."); + } + String uattr = attr.substring(attr.indexOf('.') + 1); + if (nonAttrTags.contains(uattr)) { + throw new IllegalArgumentException("attr name must not be one of " + nonAttrTags); + } + } + + private void checkAttrs(Map at) { + for (String attr : at.keySet()) { + checkAttr(attr); + } + } + + private void modAttrs() { + if (attrTypes == attrTypesInit) { + // Make modifiable. + attrTypes = new HashMap(attrTypesBacking); + } + } + + public void addAttrType(String attr, String fmt) { + checkAttr(attr); + modAttrs(); + attrTypes.put(attr, fmt); + } + + public void addAttrTypes(Map at) { + checkAttrs(at); + modAttrs(); + attrTypes.putAll(at); + } + + public Map getAttrTypes() { + if (attrTypes == attrTypesInit) { + return attrTypes; + } + return Collections.unmodifiableMap(attrTypes); + } + + public void setAttrTypes(Map at) { + checkAttrs(at); + modAttrs(); + attrTypes.keySet().retainAll(at.keySet()); + attrTypes.putAll(at); + } + + // attr format helpers + protected static boolean matchTag(int tagValue, String caseStr) { + //System.out.println("matchTag "+tagValue+" in "+caseStr); + for (int pos = 0, max = caseStr.length(), comma; + pos < max; + pos = comma + 1) { + int caseValue; + if (caseStr.charAt(pos) == '\\') { + caseValue = caseStr.charAt(pos + 1); + comma = pos + 2; + assert (comma == max || caseStr.charAt(comma) == ','); + } else { + comma = caseStr.indexOf(',', pos); + if (comma < 0) { + comma = max; + } + caseValue = Integer.parseInt(caseStr.substring(pos, comma)); + } + if (tagValue == caseValue) { + return true; + } + } + return false; + } + + protected static String[] getBodies(String type) { + ArrayList bodies = new ArrayList(); + for (int i = 0; i < type.length();) { + String body = getBody(type, i); + bodies.add(body); + i += body.length() + 2; // skip body and brackets + } + return bodies.toArray(new String[bodies.size()]); + } + + protected static String getBody(String type, int i) { + assert (type.charAt(i) == '['); + int next = ++i; // skip bracket + for (int depth = 1; depth > 0; next++) { + switch (type.charAt(next)) { + case '[': + depth++; + break; + case ']': + depth--; + break; + case '(': + next = type.indexOf(')', next); + break; + case '<': + next = type.indexOf('>', next); + break; + } + assert (next > 0); + } + --next; // get before bracket + assert (type.charAt(next) == ']'); + return type.substring(i, next); + } + + public Element makeCPDigest(int length) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (java.security.NoSuchAlgorithmException ee) { + throw new Error(ee); + } + int items = 0; + for (Element e : cpool.elements()) { + if (items == length) { + break; + } + if (cpTagNames.contains(e.getName())) { + items += 1; + md.update((byte) cpTagValue(e.getName())); + try { + md.update(e.getText().toString().getBytes(UTF8_ENCODING)); + } catch (java.io.UnsupportedEncodingException ee) { + throw new Error(ee); + } + } + } + ByteBuffer bb = ByteBuffer.wrap(md.digest()); + String l0 = Long.toHexString(bb.getLong(0)); + String l1 = Long.toHexString(bb.getLong(8)); + while (l0.length() < 16) { + l0 = "0" + l0; + } + while (l1.length() < 16) { + l1 = "0" + l1; + } + return new Element("Digest", + "length", "" + items, + "bytes", l0 + l1); + } + + public Element getCPDigest(int length) { + if (length == -1) { + length = cpool.countAll(XMLKit.elementFilter(cpTagNames)); + } + for (Element md : cpool.findAllElements("Digest").elements()) { + if (md.getAttrLong("length") == length) { + return md; + } + } + Element md = makeCPDigest(length); + cpool.add(md); + return md; + } + + public Element getCPDigest() { + return getCPDigest(-1); + } + + public boolean checkCPDigest(Element md) { + return md.equals(getCPDigest((int) md.getAttrLong("length"))); + } + + public static int computeInterfaceNum(String intMethRef) { + intMethRef = intMethRef.substring(1 + intMethRef.lastIndexOf(' ')); + if (!intMethRef.startsWith("(")) { + return -1; + } + int signum = 1; // start with one for "this" + scanSig: + for (int i = 1; i < intMethRef.length(); i++) { + char ch = intMethRef.charAt(i); + signum++; + switch (ch) { + case ')': + --signum; + break scanSig; + case 'L': + i = intMethRef.indexOf(';', i); + break; + case '[': + while (ch == '[') { + ch = intMethRef.charAt(++i); + } + if (ch == 'L') { + i = intMethRef.indexOf(';', i); + } + break; + } + } + int num = (signum << 8) | 0; + //System.out.println("computeInterfaceNum "+intMethRef+" => "+num); + return num; + } + // Protected state for representing the class file. + protected Element cfile; // + protected Element cpool; // + protected Element klass; // + protected Element currentMember; // varies during scans + protected Element currentCode; // varies during scans +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java new file mode 100644 index 00000000000..037de37e540 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- + +import java.util.*; +import java.lang.reflect.*; +import java.io.*; +import xmlkit.XMLKit.Element; +/* + * @author jrose + */ +public class ClassWriter extends ClassSyntax implements ClassSyntax.GetCPIndex { + + private static final CommandLineParser CLP = new CommandLineParser("" + + "-source: +> = \n" + + "-dest: +> = \n" + + "-encoding: +> = \n" + + "-parseBytes $ \n" + + "- *? \n" + + "\n"); + + public static void main(String[] ava) throws IOException { + ArrayList av = new ArrayList(Arrays.asList(ava)); + HashMap props = new HashMap(); + props.put("-encoding:", "UTF8"); // default + CLP.parse(av, props); + File source = asFile(props.get("-source:")); + File dest = asFile(props.get("-dest:")); + String encoding = props.get("-encoding:"); + boolean parseBytes = props.containsKey("-parseBytes"); + boolean destMade = false; + + for (String a : av) { + File f; + File inf = new File(source, a); + System.out.println("Reading " + inf); + Element e; + if (inf.getName().endsWith(".class")) { + ClassReader cr = new ClassReader(); + cr.parseBytes = parseBytes; + e = cr.readFrom(inf); + f = new File(a); + } else if (inf.getName().endsWith(".xml")) { + InputStream in = new FileInputStream(inf); + Reader inw = ClassReader.makeReader(in, encoding); + e = XMLKit.readFrom(inw); + e.findAllInTree(XMLKit.and(XMLKit.elementFilter(nonAttrTags()), + XMLKit.methodFilter(Element.method("trimText")))); + //System.out.println(e); + inw.close(); + f = new File(a.substring(0, a.length() - ".xml".length()) + ".class"); + } else { + System.out.println("Warning: unknown input " + a); + continue; + } + // Now write it: + if (!destMade) { + destMade = true; + if (dest == null) { + dest = File.createTempFile("TestOut", ".dir", new File(".")); + dest.delete(); + System.out.println("Writing results to " + dest); + } + if (!(dest.isDirectory() || dest.mkdir())) { + throw new RuntimeException("Cannot create " + dest); + } + } + File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath()); + outf.getParentFile().mkdirs(); + new ClassWriter(e).writeTo(outf); + } + } + + private static File asFile(String str) { + return (str == null) ? null : new File(str); + } + + public void writeTo(File file) throws IOException { + OutputStream out = null; + try { + out = new BufferedOutputStream(new FileOutputStream(file)); + writeTo(out); + } finally { + if (out != null) { + out.close(); + } + } + } + protected String[] callables; // varies + protected int cpoolSize = 0; + protected HashMap attrTypesByTag; + protected OutputStream out; + protected HashMap cpMap = new HashMap(); + protected ArrayList attrBufs = new ArrayList(); + + private void setupAttrTypes() { + attrTypesByTag = new HashMap(); + for (String key : attrTypes.keySet()) { + String pfx = key.substring(0, key.indexOf('.') + 1); + String val = attrTypes.get(key); + int pos = val.indexOf('<'); + if (pos >= 0) { + String tag = val.substring(pos + 1, val.indexOf('>', pos)); + attrTypesByTag.put(pfx + tag, key); + } + } + //System.out.println("attrTypesByTag: "+attrTypesByTag); + } + + protected ByteArrayOutputStream getAttrBuf() { + int nab = attrBufs.size(); + if (nab == 0) { + return new ByteArrayOutputStream(1024); + } + ByteArrayOutputStream ab = attrBufs.get(nab - 1); + attrBufs.remove(nab - 1); + return ab; + } + + protected void putAttrBuf(ByteArrayOutputStream ab) { + ab.reset(); + attrBufs.add(ab); + } + + public ClassWriter(Element root) { + this(root, null); + } + + public ClassWriter(Element root, ClassSyntax cr) { + if (cr != null) { + attrTypes = cr.attrTypes; + } + setupAttrTypes(); + if (root.getName() == "ClassFile") { + cfile = root; + cpool = root.findElement("ConstantPool"); + klass = root.findElement("Class"); + } else if (root.getName() == "Class") { + cfile = new Element("ClassFile", + new String[]{ + "magic", String.valueOf(0xCAFEBABE), + "minver", "0", "majver", "46",}); + cpool = new Element("ConstantPool"); + klass = root; + } else { + throw new IllegalArgumentException("bad element type " + root.getName()); + } + if (cpool == null) { + cpool = new Element("ConstantPool"); + } + + int cpLen = 1 + cpool.size(); + for (Element c : cpool.elements()) { + int id = (int) c.getAttrLong("id"); + int tag = cpTagValue(c.getName()); + setCPIndex(tag, c.getText().toString(), id); + switch (tag) { + case CONSTANT_Long: + case CONSTANT_Double: + cpLen += 1; + } + } + cpoolSize = cpLen; + } + + public int findCPIndex(int tag, String name) { + if (name == null) { + return 0; + } + int[] ids = cpMap.get(name.toString()); + return (ids == null) ? 0 : ids[tag]; + } + + public int getCPIndex(int tag, String name) { + //System.out.println("getCPIndex "+cpTagName(tag)+" "+name); + if (name == null) { + return 0; + } + int id = findCPIndex(tag, name); + if (id == 0) { + id = cpoolSize; + cpoolSize += 1; + setCPIndex(tag, name, id); + cpool.add(new Element(cpTagName(tag), + new String[]{"id", "" + id}, + new Object[]{name})); + int pos; + switch (tag) { + case CONSTANT_Long: + case CONSTANT_Double: + cpoolSize += 1; + break; + case CONSTANT_Class: + case CONSTANT_String: + getCPIndex(CONSTANT_Utf8, name); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + pos = name.indexOf(' '); + getCPIndex(CONSTANT_Class, name.substring(0, pos)); + getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1)); + break; + case CONSTANT_NameAndType: + pos = name.indexOf(' '); + getCPIndex(CONSTANT_Utf8, name.substring(0, pos)); + getCPIndex(CONSTANT_Utf8, name.substring(pos + 1)); + break; + } + } + return id; + } + + public void setCPIndex(int tag, String name, int id) { + //System.out.println("setCPIndex id="+id+" tag="+tag+" name="+name); + int[] ids = cpMap.get(name); + if (ids == null) { + cpMap.put(name, ids = new int[13]); + } + if (ids[tag] != 0 && ids[tag] != id) { + System.out.println("Warning: Duplicate CP entries for " + ids[tag] + " and " + id); + } + //assert(ids[tag] == 0 || ids[tag] == id); + ids[tag] = id; + } + + public int parseFlags(String flagString) { + int flags = 0; + int i = -1; + for (String[] names : modifierNames) { + ++i; + for (String name : names) { + if (name == null) { + continue; + } + int pos = flagString.indexOf(name); + if (pos >= 0) { + flags |= (1 << i); + } + } + } + return flags; + } + + public void writeTo(OutputStream realOut) throws IOException { + OutputStream headOut = realOut; + ByteArrayOutputStream tailOut = new ByteArrayOutputStream(); + + // write the body of the class file first + this.out = tailOut; + writeClass(); + + // write the file header last + this.out = headOut; + u4((int) cfile.getAttrLong("magic")); + u2((int) cfile.getAttrLong("minver")); + u2((int) cfile.getAttrLong("majver")); + writeCP(); + + // recopy the file tail + this.out = null; + tailOut.writeTo(realOut); + } + + void writeClass() throws IOException { + int flags = parseFlags(klass.getAttr("flags")); + flags ^= Modifier.SYNCHRONIZED; + u2(flags); + cpRef(CONSTANT_Class, klass.getAttr("name")); + cpRef(CONSTANT_Class, klass.getAttr("super")); + Element interfaces = klass.findAllElements("Interface"); + u2(interfaces.size()); + for (Element e : interfaces.elements()) { + cpRef(CONSTANT_Class, e.getAttr("name")); + } + for (int isMethod = 0; isMethod <= 1; isMethod++) { + Element members = klass.findAllElements(isMethod != 0 ? "Method" : "Field"); + u2(members.size()); + for (Element m : members.elements()) { + writeMember(m, isMethod != 0); + } + } + writeAttributesFor(klass); + } + + private void writeMember(Element member, boolean isMethod) throws IOException { + //System.out.println("writeMember "+member); + u2(parseFlags(member.getAttr("flags"))); + cpRef(CONSTANT_Utf8, member.getAttr("name")); + cpRef(CONSTANT_Utf8, member.getAttr("type")); + writeAttributesFor(member); + } + + protected void writeAttributesFor(Element x) throws IOException { + LinkedHashSet attrNames = new LinkedHashSet(); + for (Element e : x.elements()) { + attrNames.add(e.getName()); // uniquifying + } + attrNames.removeAll(nonAttrTags()); + u2(attrNames.size()); + if (attrNames.isEmpty()) { + return; + } + Element prevCurrent; + if (x.getName() == "Code") { + prevCurrent = currentCode; + currentCode = x; + } else { + prevCurrent = currentMember; + currentMember = x; + } + OutputStream realOut = this.out; + for (String utag : attrNames) { + String qtag = x.getName() + "." + utag; + String wtag = "*." + utag; + String key = attrTypesByTag.get(qtag); + if (key == null) { + key = attrTypesByTag.get(wtag); + } + String type = attrTypes.get(key); + //System.out.println("tag "+qtag+" => key "+key+"; type "+type); + Element attrs = x.findAllElements(utag); + ByteArrayOutputStream attrBuf = getAttrBuf(); + if (type == null) { + if (attrs.size() != 1 || !attrs.get(0).equals(new Element(utag))) { + System.out.println("Warning: No attribute type description: " + qtag); + } + key = wtag; + } else { + try { + this.out = attrBuf; + // unparse according to type desc. + if (type.equals("...")) { + writeCode((Element) attrs.get(0)); // assume only 1 + } else if (type.equals("...")) { + writeStackMap(attrs, false); + } else if (type.equals("...")) { + writeStackMap(attrs, true); + } else if (type.startsWith("[")) { + writeAttributeRecursive(attrs, type); + } else { + writeAttribute(attrs, type); + } + } finally { + //System.out.println("Attr Bytes = \""+attrBuf.toString(EIGHT_BIT_CHAR_ENCODING).replace('"', (char)('"'|0x80))+"\""); + this.out = realOut; + } + } + cpRef(CONSTANT_Utf8, key.substring(key.indexOf('.') + 1)); + u4(attrBuf.size()); + attrBuf.writeTo(out); + putAttrBuf(attrBuf); + } + if (x.getName() == "Code") { + currentCode = prevCurrent; + } else { + currentMember = prevCurrent; + } + } + + private void writeAttributeRecursive(Element aval, String type) throws IOException { + assert (callables == null); + callables = getBodies(type); + writeAttribute(aval, callables[0]); + callables = null; + } + + private void writeAttribute(Element aval, String type) throws IOException { + //System.out.println("writeAttribute "+aval+" using "+type); + String nextAttrName = null; + boolean afterElemHead = false; + for (int len = type.length(), next, i = 0; i < len; i = next) { + int value; + char intKind; + int tag; + int sigChar; + String attrValue; + switch (type.charAt(i)) { + case '<': + assert (nextAttrName == null); + next = type.indexOf('>', i); + String form = type.substring(i + 1, next++); + if (form.indexOf('=') < 0) { + // elem_placement = '<' elemname '>' + if (aval.isAnonymous()) { + assert (aval.size() == 1); + aval = (Element) aval.get(0); + } + assert (aval.getName().equals(form)) : aval + " // " + form; + afterElemHead = true; + } else { + // attr_placement = '(' attrname '=' (value)? ')' + int eqPos = form.indexOf('='); + assert (eqPos >= 0); + nextAttrName = form.substring(0, eqPos).intern(); + if (eqPos != form.length() - 1) { + // value is implicit, not placed in file + nextAttrName = null; + } + afterElemHead = false; + } + continue; + case '(': + next = type.indexOf(')', ++i); + int callee = Integer.parseInt(type.substring(i, next++)); + writeAttribute(aval, callables[callee]); + continue; + case 'N': // replication = 'N' int '[' type ... ']' + { + assert (nextAttrName == null); + afterElemHead = false; + char countType = type.charAt(i + 1); + next = i + 2; + String type1 = getBody(type, next); + Element elems = aval; + if (type1.startsWith("<")) { + // Select only matching members of aval. + String elemName = type1.substring(1, type1.indexOf('>')); + elems = aval.findAllElements(elemName); + } + putInt(elems.size(), countType); + next += type1.length() + 2; // skip body and brackets + for (Element elem : elems.elements()) { + writeAttribute(elem, type1); + } + } + continue; + case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']' + // write the value + value = (int) aval.getAttrLong("tag"); + assert (aval.getAttr("tag") != null) : aval; + intKind = type.charAt(++i); + if (intKind == 'S') { + intKind = type.charAt(++i); + } + putInt(value, intKind); + nextAttrName = null; + afterElemHead = false; + ++i; // skip the int type char + // union_case = '(' ('-')? digit+ ')' '[' body ']' + for (boolean foundCase = false;;) { + assert (type.charAt(i) == '('); + next = type.indexOf(')', ++i); + assert (next >= i); + String caseStr = type.substring(i, next++); + String type1 = getBody(type, next); + next += type1.length() + 2; // skip body and brackets + boolean lastCase = (caseStr.length() == 0); + if (!foundCase + && (lastCase || matchTag(value, caseStr))) { + foundCase = true; + // Execute this body. + writeAttribute(aval, type1); + } + if (lastCase) { + break; + } + } + continue; + case 'B': + case 'H': + case 'I': // int = oneof "BHI" + value = (int) aval.getAttrLong(nextAttrName); + intKind = type.charAt(i); + next = i + 1; + break; + case 'K': + sigChar = type.charAt(i + 1); + if (sigChar == 'Q') { + assert (currentMember.getName() == "Field"); + assert (aval.getName() == "ConstantValue"); + String sig = currentMember.getAttr("type"); + sigChar = sig.charAt(0); + switch (sigChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + sigChar = 'I'; + break; + } + } + switch (sigChar) { + case 'I': + tag = CONSTANT_Integer; + break; + case 'J': + tag = CONSTANT_Long; + break; + case 'F': + tag = CONSTANT_Float; + break; + case 'D': + tag = CONSTANT_Double; + break; + case 'L': + tag = CONSTANT_String; + break; + default: + assert (false); + tag = 0; + } + assert (type.charAt(i + 2) == 'H'); // only H works for now + next = i + 3; + assert (afterElemHead || nextAttrName != null); + //System.out.println("get attr "+nextAttrName+" in "+aval); + if (nextAttrName != null) { + attrValue = aval.getAttr(nextAttrName); + assert (attrValue != null); + } else { + assert (aval.isText()) : aval; + attrValue = aval.getText().toString(); + } + value = getCPIndex(tag, attrValue); + intKind = 'H'; //type.charAt(i+2); + break; + case 'R': + sigChar = type.charAt(i + 1); + switch (sigChar) { + case 'C': + tag = CONSTANT_Class; + break; + case 'S': + tag = CONSTANT_Utf8; + break; + case 'D': + tag = CONSTANT_Class; + break; + case 'F': + tag = CONSTANT_Fieldref; + break; + case 'M': + tag = CONSTANT_Methodref; + break; + case 'I': + tag = CONSTANT_InterfaceMethodref; + break; + case 'U': + tag = CONSTANT_Utf8; + break; + //case 'Q': tag = CONSTANT_Class; break; + default: + assert (false); + tag = 0; + } + assert (type.charAt(i + 2) == 'H'); // only H works for now + next = i + 3; + assert (afterElemHead || nextAttrName != null); + //System.out.println("get attr "+nextAttrName+" in "+aval); + if (nextAttrName != null) { + attrValue = aval.getAttr(nextAttrName); + } else if (aval.hasText()) { + attrValue = aval.getText().toString(); + } else { + attrValue = null; + } + value = getCPIndex(tag, attrValue); + intKind = 'H'; //type.charAt(i+2); + break; + case 'P': // bci = 'P' int + case 'S': // signed_int = 'S' int + next = i + 2; + value = (int) aval.getAttrLong(nextAttrName); + intKind = type.charAt(i + 1); + break; + case 'F': + next = i + 2; + value = parseFlags(aval.getAttr(nextAttrName)); + intKind = type.charAt(i + 1); + break; + default: + throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type); + } + // write the value + putInt(value, intKind); + nextAttrName = null; + afterElemHead = false; + } + assert (nextAttrName == null); + } + + private void putInt(int x, char ch) throws IOException { + switch (ch) { + case 'B': + u1(x); + break; + case 'H': + u2(x); + break; + case 'I': + u4(x); + break; + } + assert ("BHI".indexOf(ch) >= 0); + } + + private void writeCode(Element code) throws IOException { + //System.out.println("writeCode "+code); + //Element m = new Element(currentMember); m.remove(code); + //System.out.println(" in "+m); + int stack = (int) code.getAttrLong("stack"); + int local = (int) code.getAttrLong("local"); + Element bytes = code.findElement("Bytes"); + Element insns = code.findElement("Instructions"); + String bytecodes; + if (insns == null) { + bytecodes = bytes.getText().toString(); + } else { + bytecodes = InstructionSyntax.assemble(insns, this); + // Cache the assembled bytecodes: + bytes = new Element("Bytes", (String[]) null, bytecodes); + code.add(0, bytes); + } + u2(stack); + u2(local); + int length = bytecodes.length(); + u4(length); + for (int i = 0; i < length; i++) { + u1((byte) bytecodes.charAt(i)); + } + Element handlers = code.findAllElements("Handler"); + u2(handlers.size()); + for (Element handler : handlers.elements()) { + int start = (int) handler.getAttrLong("start"); + int end = (int) handler.getAttrLong("end"); + int catsh = (int) handler.getAttrLong("catch"); + u2(start); + u2(end); + u2(catsh); + cpRef(CONSTANT_Class, handler.getAttr("class")); + } + writeAttributesFor(code); + } + + protected void writeStackMap(Element attrs, boolean hasXOption) throws IOException { + Element bytes = currentCode.findElement("Bytes"); + assert (bytes != null && bytes.size() == 1); + int byteLength = ((String) bytes.get(0)).length(); + boolean uoffsetIsU4 = (byteLength >= (1 << 16)); + boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16); + boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16); + if (uoffsetIsU4) { + u4(attrs.size()); + } else { + u2(attrs.size()); + } + for (Element frame : attrs.elements()) { + int bci = (int) frame.getAttrLong("bci"); + if (uoffsetIsU4) { + u4(bci); + } else { + u2(bci); + } + if (hasXOption) { + u1((int) frame.getAttrLong("flags")); + } + // Scan local and stack types in this frame: + final int LOCALS = 0, STACK = 1; + for (int j = LOCALS; j <= STACK; j++) { + Element types = frame.findElement(j == LOCALS ? "Local" : "Stack"); + int typeSize = (types == null) ? 0 : types.size(); + if (j == LOCALS) { + if (ulocalvarIsU4) { + u4(typeSize); + } else { + u2(typeSize); + } + } else { // STACK + if (ustackIsU4) { + u4(typeSize); + } else { + u2(typeSize); + } + } + if (types == null) { + continue; + } + for (Element type : types.elements()) { + int tag = itemTagValue(type.getName()); + u1(tag); + switch (tag) { + case ITEM_Object: + cpRef(CONSTANT_Class, type.getAttr("class")); + break; + case ITEM_Uninitialized: + case ITEM_ReturnAddress: { + int offset = (int) type.getAttrLong("bci"); + if (uoffsetIsU4) { + u4(offset); + } else { + u2(offset); + } + } + break; + } + } + } + } + } + + public void writeCP() throws IOException { + int cpLen = cpoolSize; + u2(cpLen); + ByteArrayOutputStream buf = getAttrBuf(); + for (Element c : cpool.elements()) { + if (!c.isText()) { + System.out.println("## !isText " + c); + } + int id = (int) c.getAttrLong("id"); + int tag = cpTagValue(c.getName()); + String name = c.getText().toString(); + int pos; + u1(tag); + switch (tag) { + case CONSTANT_Utf8: { + int done = 0; + buf.reset(); + int nameLen = name.length(); + while (done < nameLen) { + int next = name.indexOf((char) 0, done); + if (next < 0) { + next = nameLen; + } + if (done < next) { + buf.write(name.substring(done, next).getBytes(UTF8_ENCODING)); + } + if (next < nameLen) { + buf.write(0300); + buf.write(0200); + next++; + } + done = next; + } + u2(buf.size()); + buf.writeTo(out); + } + break; + case CONSTANT_Integer: + u4(Integer.parseInt(name)); + break; + case CONSTANT_Float: + u4(Float.floatToIntBits(Float.parseFloat(name))); + break; + case CONSTANT_Long: + u8(Long.parseLong(name)); + //i += 1; // no need: extra cp slot is implicit + break; + case CONSTANT_Double: + u8(Double.doubleToLongBits(Double.parseDouble(name))); + //i += 1; // no need: extra cp slot is implicit + break; + case CONSTANT_Class: + case CONSTANT_String: + u2(getCPIndex(CONSTANT_Utf8, name)); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + pos = name.indexOf(' '); + u2(getCPIndex(CONSTANT_Class, name.substring(0, pos))); + u2(getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1))); + break; + case CONSTANT_NameAndType: + pos = name.indexOf(' '); + u2(getCPIndex(CONSTANT_Utf8, name.substring(0, pos))); + u2(getCPIndex(CONSTANT_Utf8, name.substring(pos + 1))); + break; + } + } + putAttrBuf(buf); + } + + public void cpRef(int tag, String name) throws IOException { + u2(getCPIndex(tag, name)); + } + + public void u8(long x) throws IOException { + u4((int) (x >>> 32)); + u4((int) (x >>> 0)); + } + + public void u4(int x) throws IOException { + u2(x >>> 16); + u2(x >>> 0); + } + + public void u2(int x) throws IOException { + u1(x >>> 8); + u1(x >>> 0); + } + + public void u1(int x) throws IOException { + out.write(x & 0xFF); + } +} + diff --git a/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java new file mode 100644 index 00000000000..852b4b3f4a3 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- + +import java.util.*; +/* + * @author jrose + */ +public class CommandLineParser { + + public CommandLineParser(String optionString) { + setOptionMap(optionString); + } + TreeMap optionMap; + + public void setOptionMap(String options) { + // Convert options string into optLines dictionary. + TreeMap optmap = new TreeMap(); + loadOptmap: + for (String optline : options.split("\n")) { + String[] words = optline.split("\\p{Space}+"); + if (words.length == 0) { + continue loadOptmap; + } + String opt = words[0]; + words[0] = ""; // initial word is not a spec + if (opt.length() == 0 && words.length >= 1) { + opt = words[1]; // initial "word" is empty due to leading ' ' + words[1] = ""; + } + if (opt.length() == 0) { + continue loadOptmap; + } + String[] prevWords = optmap.put(opt, words); + if (prevWords != null) { + throw new RuntimeException("duplicate option: " + + optline.trim()); + } + } + optionMap = optmap; + } + + public String getOptionMap() { + TreeMap optmap = optionMap; + StringBuffer sb = new StringBuffer(); + for (String opt : optmap.keySet()) { + sb.append(opt); + for (String spec : optmap.get(opt)) { + sb.append(' ').append(spec); + } + sb.append('\n'); + } + return sb.toString(); + } + + /** + * Remove a set of command-line options from args, + * storing them in the properties map in a canonicalized form. + */ + public String parse(List args, Map properties) { + //System.out.println(args+" // "+properties); + + String resultString = null; + TreeMap optmap = optionMap; + + // State machine for parsing a command line. + ListIterator argp = args.listIterator(); + ListIterator pbp = new ArrayList().listIterator(); + doArgs: + for (;;) { + // One trip through this loop per argument. + // Multiple trips per option only if several options per argument. + String arg; + if (pbp.hasPrevious()) { + arg = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + arg = argp.next(); + } else { + // No more arguments at all. + break doArgs; + } + tryOpt: + for (int optlen = arg.length();; optlen--) { + // One time through this loop for each matching arg prefix. + String opt; + // Match some prefix of the argument to a key in optmap. + findOpt: + for (;;) { + opt = arg.substring(0, optlen); + if (optmap.containsKey(opt)) { + break findOpt; + } + if (optlen == 0) { + break tryOpt; + } + // Decide on a smaller prefix to search for. + SortedMap pfxmap = optmap.headMap(opt); + // pfxmap.lastKey is no shorter than any prefix in optmap. + int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length(); + optlen = Math.min(len, optlen - 1); + opt = arg.substring(0, optlen); + // (Note: We could cut opt down to its common prefix with + // pfxmap.lastKey, but that wouldn't save many cycles.) + } + opt = opt.intern(); + assert (arg.startsWith(opt)); + assert (opt.length() == optlen); + String val = arg.substring(optlen); // arg == opt+val + + // Execute the option processing specs for this opt. + // If no actions are taken, then look for a shorter prefix. + boolean didAction = false; + boolean isError = false; + + int pbpMark = pbp.nextIndex(); // in case of backtracking + String[] specs = optmap.get(opt); + eachSpec: + for (String spec : specs) { + if (spec.length() == 0) { + continue eachSpec; + } + if (spec.startsWith("#")) { + break eachSpec; + } + int sidx = 0; + char specop = spec.charAt(sidx++); + + // Deal with '+'/'*' prefixes (spec conditions). + boolean ok; + switch (specop) { + case '+': + // + means we want an non-empty val suffix. + ok = (val.length() != 0); + specop = spec.charAt(sidx++); + break; + case '*': + // * means we accept empty or non-empty + ok = true; + specop = spec.charAt(sidx++); + break; + default: + // No condition prefix means we require an exact + // match, as indicated by an empty val suffix. + ok = (val.length() == 0); + break; + } + if (!ok) { + continue eachSpec; + } + + String specarg = spec.substring(sidx); + switch (specop) { + case '.': // terminate the option sequence + resultString = (specarg.length() != 0) ? specarg.intern() : opt; + break doArgs; + case '?': // abort the option sequence + resultString = (specarg.length() != 0) ? specarg.intern() : arg; + isError = true; + break eachSpec; + case '@': // change the effective opt name + opt = specarg.intern(); + break; + case '>': // shift remaining arg val to next arg + pbp.add(specarg + val); // push a new argument + val = ""; + break; + case '!': // negation option + String negopt = (specarg.length() != 0) ? specarg.intern() : opt; + properties.remove(negopt); + properties.put(negopt, null); // leave placeholder + didAction = true; + break; + case '$': // normal "boolean" option + String boolval; + if (specarg.length() != 0) { + // If there is a given spec token, store it. + boolval = specarg; + } else { + String old = properties.get(opt); + if (old == null || old.length() == 0) { + boolval = "1"; + } else { + // Increment any previous value as a numeral. + boolval = "" + (1 + Integer.parseInt(old)); + } + } + properties.put(opt, boolval); + didAction = true; + break; + case '=': // "string" option + case '&': // "collection" option + // Read an option. + boolean append = (specop == '&'); + String strval; + if (pbp.hasPrevious()) { + strval = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + strval = argp.next(); + } else { + resultString = arg + " ?"; + isError = true; + break eachSpec; + } + if (append) { + String old = properties.get(opt); + if (old != null) { + // Append new val to old with embedded delim. + String delim = specarg; + if (delim.length() == 0) { + delim = " "; + } + strval = old + specarg + strval; + } + } + properties.put(opt, strval); + didAction = true; + break; + default: + throw new RuntimeException("bad spec for " + + opt + ": " + spec); + } + } + + // Done processing specs. + if (didAction && !isError) { + continue doArgs; + } + + // The specs should have done something, but did not. + while (pbp.nextIndex() > pbpMark) { + // Remove anything pushed during these specs. + pbp.previous(); + pbp.remove(); + } + + if (isError) { + throw new IllegalArgumentException(resultString); + } + + if (optlen == 0) { + // We cannot try a shorter matching option. + break tryOpt; + } + } + + // If we come here, there was no matching option. + // So, push back the argument, and return to caller. + pbp.add(arg); + break doArgs; + } + // Report number of arguments consumed. + args.subList(0, argp.nextIndex()).clear(); + // Report any unconsumed partial argument. + while (pbp.hasPrevious()) { + args.add(0, pbp.previous()); + } + //System.out.println(args+" // "+properties+" -> "+resultString); + return resultString; + } +} diff --git a/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java new file mode 100644 index 00000000000..cbe34e6b960 --- /dev/null +++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2010, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- + +import xmlkit.XMLKit.Element; +import java.util.HashMap; +/* + * @author jrose + */ +abstract class InstructionAssembler extends InstructionSyntax { + + InstructionAssembler() { + } + + public static String assemble(Element instructions, String pcAttrName, + ClassSyntax.GetCPIndex getCPI) { + int insCount = instructions.size(); + Element[] insElems = new Element[insCount]; + int[] elemToIndexMap; + int[] insLocs; + byte[] ops = new byte[insCount]; + int[] operands = new int[insCount]; + boolean[] isWide = new boolean[insCount]; + int[] branches; + int[] branchInsLocs; + HashMap labels = new HashMap(); + + final int WIDE = 0xc4; + final int GOTO = 0xa7; + final int GOTO_W = 0xc8; + final int GOTO_LEN = 3; + final int GOTO_W_LEN = 5; + assert ("wide".equals(bcNames[WIDE])); + assert ("goto".equals(bcNames[GOTO])); + assert ("goto_w".equals(bcNames[GOTO_W])); + assert (bcFormats[GOTO].length() == GOTO_LEN); + assert (bcFormats[GOTO_W].length() == GOTO_W_LEN); + + // Unpack instructions into temp. arrays, and find branches and labels. + { + elemToIndexMap = (pcAttrName != null) ? new int[insCount] : null; + int[] buffer = operands; + int id = 0; + int branchCount = 0; + for (int i = 0; i < insCount; i++) { + Element ins = (Element) instructions.get(i); + if (elemToIndexMap != null) { + elemToIndexMap[i] = (ins.getAttr(pcAttrName) != null ? id : -1); + } + String lab = ins.getAttr("pc"); + if (lab != null) { + labels.put(lab, String.valueOf(id)); + } + int op = opCode(ins.getName()); + if (op < 0) { + assert (ins.getAttr(pcAttrName) != null + || ins.getName().equals("label")); + continue; // delete PC holder element + } + if (op == WIDE) { //0xc4 + isWide[id] = true; // force wide format + continue; + } + if (bcFormats[op].indexOf('o') >= 0) { + buffer[branchCount++] = id; + } + if (bcFormats[op] == bcWideFormats[op]) { + isWide[id] = false; + } + insElems[id] = ins; + ops[id] = (byte) op; + id++; + } + insCount = id; // maybe we deleted some wide prefixes, etc. + branches = new int[branchCount + 1]; + System.arraycopy(buffer, 0, branches, 0, branchCount); + branches[branchCount] = -1; // sentinel + } + + // Compute instruction sizes. These sizes are final, + // except for branch instructions, which may need lengthening. + // Some instructions (ldc, bipush, iload, iinc) are automagically widened. + insLocs = new int[insCount + 1]; + int loc = 0; + for (int bn = 0, id = 0; id < insCount; id++) { + insLocs[id] = loc; + Element ins = insElems[id]; + int op = ops[id] & 0xFF; + String format = opFormat(op, isWide[id]); + // Make sure operands fit within the given format. + for (int j = 1, jlimit = format.length(); j < jlimit; j++) { + char fc = format.charAt(j); + int x = 0; + switch (fc) { + case 'l': + x = (int) ins.getAttrLong("loc"); + assert (x >= 0); + if (x > 0xFF && !isWide[id]) { + isWide[id] = true; + format = opFormat(op, isWide[id]); + } + assert (x <= 0xFFFF); + break; + case 'k': + char fc2 = format.charAt(Math.min(j + 1, format.length() - 1)); + x = getCPIndex(ins, fc2, getCPI); + if (x > 0xFF && j == jlimit - 1) { + assert (op == 0x12); //ldc + ops[id] = (byte) (op = 0x13); //ldc_w + format = opFormat(op); + } + assert (x <= 0xFFFF); + j++; // skip type-of-constant marker + break; + case 'x': + x = (int) ins.getAttrLong("num"); + assert (x >= 0 && x <= ((j == jlimit - 1) ? 0xFF : 0xFFFF)); + break; + case 's': + x = (int) ins.getAttrLong("num"); + if (x != (byte) x && j == jlimit - 1) { + switch (op) { + case 0x10: //bipush + ops[id] = (byte) (op = 0x11); //sipush + break; + case 0x84: //iinc + isWide[id] = true; + format = opFormat(op, isWide[id]); + break; + default: + assert (false); // cannot lengthen + } + } + // unsign the value now, to make later steps clearer + if (j == jlimit - 1) { + assert (x == (byte) x); + x = x & 0xFF; + } else { + assert (x == (short) x); + x = x & 0xFFFF; + } + break; + case 'o': + assert (branches[bn] == id); + bn++; + // make local copies of the branches, and fix up labels + insElems[id] = ins = new Element(ins); + String newLab = labels.get(ins.getAttr("lab")); + assert (newLab != null); + ins.setAttr("lab", newLab); + int prevCas = 0; + int k = 0; + for (Element cas : ins.elements()) { + assert (cas.getName().equals("Case")); + ins.set(k++, cas = new Element(cas)); + newLab = labels.get(cas.getAttr("lab")); + assert (newLab != null); + cas.setAttr("lab", newLab); + int thisCas = (int) cas.getAttrLong("num"); + assert (op == 0xab + || op == 0xaa && (k == 0 || thisCas == prevCas + 1)); + prevCas = thisCas; + } + break; + case 't': + // switch table is represented as Switch.Case sub-elements + break; + default: + assert (false); + } + operands[id] = x; // record operand (last if there are 2) + // skip redundant chars + while (j + 1 < jlimit && format.charAt(j + 1) == fc) { + ++j; + } + } + + switch (op) { + case 0xaa: //tableswitch + loc = switchBase(loc); + loc += 4 * (3 + ins.size()); + break; + case 0xab: //lookupswitch + loc = switchBase(loc); + loc += 4 * (2 + 2 * ins.size()); + break; + default: + if (isWide[id]) { + loc++; // 'wide' opcode prefix + } + loc += format.length(); + break; + } + } + insLocs[insCount] = loc; + + // compute branch offsets, and see if any branches need expansion + for (int maxTries = 9, tries = 0;; ++tries) { + boolean overflowing = false; + boolean[] branchExpansions = null; + for (int bn = 0; bn < branches.length - 1; bn++) { + int id = branches[bn]; + Element ins = insElems[id]; + int insSize = insLocs[id + 1] - insLocs[id]; + int origin = insLocs[id]; + int target = insLocs[(int) ins.getAttrLong("lab")]; + int offset = target - origin; + operands[id] = offset; + //System.out.println("branch id="+id+" len="+insSize+" to="+target+" offset="+offset); + assert (insSize == GOTO_LEN || insSize == GOTO_W_LEN || ins.getName().indexOf("switch") > 0); + boolean thisOverflow = (insSize == GOTO_LEN && (offset != (short) offset)); + if (thisOverflow && !overflowing) { + overflowing = true; + branchExpansions = new boolean[branches.length]; + } + if (thisOverflow || tries == maxTries - 1) { + // lengthen the branch + assert (!(thisOverflow && isWide[id])); + isWide[id] = true; + branchExpansions[bn] = true; + } + } + if (!overflowing) { + break; // done, usually on first try + } + assert (tries <= maxTries); + + // Walk over all instructions, expanding branches and updating locations. + int fixup = 0; + for (int bn = 0, id = 0; id < insCount; id++) { + insLocs[id] += fixup; + if (branches[bn] == id) { + int op = ops[id] & 0xFF; + int wop; + boolean invert; + if (branchExpansions[bn]) { + switch (op) { + case GOTO: //0xa7 + wop = GOTO_W; //0xc8 + invert = false; + break; + case 0xa8: //jsr + wop = 0xc9; //jsr_w + invert = false; + break; + default: + wop = invertBranchOp(op); + invert = true; + break; + } + assert (op != wop); + ops[id] = (byte) wop; + isWide[id] = invert; + if (invert) { + fixup += GOTO_W_LEN; //branch around a wide goto + } else { + fixup += (GOTO_W_LEN - GOTO_LEN); + } + // done expanding: ops and isWide reflect the decision + } + bn++; + } + } + insLocs[insCount] += fixup; + } + // we know the layout now + + // notify the caller of offsets, if requested + if (elemToIndexMap != null) { + for (int i = 0; i < elemToIndexMap.length; i++) { + int id = elemToIndexMap[i]; + if (id >= 0) { + Element ins = (Element) instructions.get(i); + ins.setAttr(pcAttrName, "" + insLocs[id]); + } + } + elemToIndexMap = null; // release the pointer + } + + // output the bytes + StringBuffer sbuf = new StringBuffer(insLocs[insCount]); + for (int bn = 0, id = 0; id < insCount; id++) { + //System.out.println("output id="+id+" loc="+insLocs[id]+" len="+(insLocs[id+1]-insLocs[id])+" #sbuf="+sbuf.length()); + assert (sbuf.length() == insLocs[id]); + Element ins; + int pc = insLocs[id]; + int nextpc = insLocs[id + 1]; + int op = ops[id] & 0xFF; + int opnd = operands[id]; + String format; + if (branches[bn] == id) { + bn++; + sbuf.append((char) op); + if (isWide[id]) { + // emit