From e756ba0d849dc61a980bfdc4bbae06fde8905327 Mon Sep 17 00:00:00 2001 From: Erik Ostermueller Date: Fri, 6 Dec 2013 00:37:58 -0600 Subject: [PATCH 1/2] new [gzip-true parm will gzip traced message format on the wire --- org.intrace/lib/intrace-agent.jar | Bin 371863 -> 386624 bytes .../src/org/intrace/agent/AgentSettings.java | 22 +- .../org/intrace/agent/ClassTransformer.java | 10 + .../agent/server/AgentClientConnection.java | 6 +- .../intrace/client/gui/helper/InTraceUI.java | 5 + .../gui/helper/NetworkDataReceiverThread.java | 8 + .../client/gui/helper/ParsedSettingsData.java | 11 + .../output/NetworkDataSenderThread.java | 5 + .../intrace/shared/AgentConfigConstants.java | 2 + .../src/org/intrace/shared/Base64.java | 2064 +++++++++++++++++ .../org/intracetest/agent/AgentTest.java | 39 +- .../intracetest/agent/CompressionTest.java | 185 ++ .../intracetest/agent/TraceSettingsTest.java | 48 + 13 files changed, 2402 insertions(+), 3 deletions(-) create mode 100644 org.intrace/src/org/intrace/shared/Base64.java create mode 100644 org.intrace/testsrc/org/intracetest/agent/CompressionTest.java create mode 100644 org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java diff --git a/org.intrace/lib/intrace-agent.jar b/org.intrace/lib/intrace-agent.jar index 47c11116902f1cd770473ea57a441a85b45ecd54..19af344e539fb7ecf802266ad42a2b8bbdeef064 100644 GIT binary patch delta 51391 zcmZ6yQ;;r9uq-;ZZQHhO+qTX1jcwc38r!yQd)C-G|2{YF!#)okol$Sy(UDnMRh_

3NhWKn_6L2LC|BNfs7SK>^s;g)bJe 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 settingsMap = new HashMap(); settingsMap.put(AgentConfigConstants.INSTRU_ENABLED, Boolean.toString(instruEnabled)); + settingsMap.put(AgentConfigConstants.GZIP, + Boolean.toString(gzipEnabled)); settingsMap.put(AgentConfigConstants.CLASS_REGEX, getPatternString(classRegex)); settingsMap.put(AgentConfigConstants.EXCLUDE_CLASS_REGEX, diff --git a/org.intrace/src/org/intrace/agent/ClassTransformer.java b/org.intrace/src/org/intrace/agent/ClassTransformer.java index e74440a..1ca65cd 100644 --- a/org.intrace/src/org/intrace/agent/ClassTransformer.java +++ b/org.intrace/src/org/intrace/agent/ClassTransformer.java @@ -27,6 +27,7 @@ import org.intrace.agent.server.AgentServer; import org.intrace.output.AgentHelper; import org.intrace.output.InstruRunnable; import org.intrace.output.trace.TraceHandler; +import org.intrace.output.trace.TraceSettings; import org.intrace.shared.AgentConfigConstants; import org.objectweb.asm.ClassReader; @@ -482,6 +483,9 @@ public class ClassTransformer implements ClassFileTransformer Integer.toString(modifiedClasses.size())); return settingsMap; } + public boolean isGzipEnabled() { + return settings.isGzipEnabled(); + } /** * Handle a message and return a response. @@ -507,6 +511,12 @@ public class ClassTransformer implements ClassFileTransformer // System.out.println("## Settings Changed"); setInstrumentationEnabled(settings.isInstrumentationEnabled()); } + else if (oldSettings.isGzipEnabled() != settings.isGzipEnabled()) + { + //For coding simplicity and user clarity, the gzip parameter can only be set on agent side + //So, return to the old setting, which originated with the intrace agent command line parameter -- [gzip-true + settings.setGzipEnabled(oldSettings.isGzipEnabled()); + } else if (!Arrays.equals(oldSettings.getClassRegex(), settings.getClassRegex())) { // System.out.println("## Settings Changed"); diff --git a/org.intrace/src/org/intrace/agent/server/AgentClientConnection.java b/org.intrace/src/org/intrace/agent/server/AgentClientConnection.java index d811fc9..4224ea5 100644 --- a/org.intrace/src/org/intrace/agent/server/AgentClientConnection.java +++ b/org.intrace/src/org/intrace/agent/server/AgentClientConnection.java @@ -25,7 +25,11 @@ public class AgentClientConnection extends InstruRunnable { private final Socket connectedClient; private final ClassTransformer transformer; - private boolean traceConnEstablished = false; + public ClassTransformer getTransformer() { + return transformer; +} + +private boolean traceConnEstablished = false; private final Object traceConnLock = new Object(); /** diff --git a/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java b/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java index 3cb5046..cfc1f8d 100644 --- a/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java +++ b/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java @@ -2026,6 +2026,11 @@ public class InTraceUI implements ISocketCallback, IControlConnectionListener { return settingsData.netOutEnabled; } + @Override + public boolean isGzipEnabled() + { + return settingsData.gzipEnabled; + } }; networkTraceThread = new NetworkDataReceiverThread(remoteAddress, networkTracePort, config, outputTabs.textOutputTab.filterThread); diff --git a/org.intrace/src/org/intrace/client/gui/helper/NetworkDataReceiverThread.java b/org.intrace/src/org/intrace/client/gui/helper/NetworkDataReceiverThread.java index d34e915..25970aa 100644 --- a/org.intrace/src/org/intrace/client/gui/helper/NetworkDataReceiverThread.java +++ b/org.intrace/src/org/intrace/client/gui/helper/NetworkDataReceiverThread.java @@ -6,11 +6,15 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import org.intrace.shared.Base64; + + public class NetworkDataReceiverThread implements Runnable { public static interface INetworkOutputConfig { public boolean isNetOutputEnabled(); + public boolean isGzipEnabled(); } private final Socket traceSocket; @@ -53,6 +57,10 @@ public class NetworkDataReceiverThread implements Runnable { if (outputConfig.isNetOutputEnabled()) { + if (outputConfig.isGzipEnabled()) { + byte[] tmp = Base64.decode(traceLine); + traceLine = new String(tmp); + } traceThread.addTraceLine(traceLine); } } diff --git a/org.intrace/src/org/intrace/client/gui/helper/ParsedSettingsData.java b/org.intrace/src/org/intrace/client/gui/helper/ParsedSettingsData.java index 7782e85..55cac4f 100644 --- a/org.intrace/src/org/intrace/client/gui/helper/ParsedSettingsData.java +++ b/org.intrace/src/org/intrace/client/gui/helper/ParsedSettingsData.java @@ -16,6 +16,7 @@ public class ParsedSettingsData public boolean entryExitEnabled; public boolean branchEnabled; + public boolean gzipEnabled = false; public boolean truncArraysEnabled; public boolean argsEnabled; public boolean stdOutEnabled; @@ -35,6 +36,15 @@ public class ParsedSettingsData { this.settingsMap = settingsMap; + if ("true".equals(settingsMap.get(AgentConfigConstants.GZIP))) + { + gzipEnabled = true; + } + else + { + gzipEnabled = false; + } + classRegex = settingsMap.get(AgentConfigConstants.CLASS_REGEX); classExcludeRegex = settingsMap .get(AgentConfigConstants.EXCLUDE_CLASS_REGEX); @@ -86,6 +96,7 @@ public class ParsedSettingsData branchEnabled = false; } + if ("true".equals(settingsMap.get(TraceConfigConstants.ARG))) { argsEnabled = true; diff --git a/org.intrace/src/org/intrace/output/NetworkDataSenderThread.java b/org.intrace/src/org/intrace/output/NetworkDataSenderThread.java index 5f211e2..a1e5416 100644 --- a/org.intrace/src/org/intrace/output/NetworkDataSenderThread.java +++ b/org.intrace/src/org/intrace/output/NetworkDataSenderThread.java @@ -11,6 +11,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.intrace.agent.server.AgentClientConnection; +import org.intrace.shared.Base64; public class NetworkDataSenderThread extends InstruRunnable { @@ -100,6 +101,10 @@ public class NetworkDataSenderThread extends InstruRunnable Object traceLine = outgoingData.poll(5, TimeUnit.SECONDS); if (traceLine != null) { + if (traceLine instanceof String && connection.getTransformer().isGzipEnabled()) { + String tmp = (String)traceLine; + traceLine = Base64.encodeBytes(tmp.getBytes(), Base64.GZIP); + } traceWriter.writeObject(traceLine); } else diff --git a/org.intrace/src/org/intrace/shared/AgentConfigConstants.java b/org.intrace/src/org/intrace/shared/AgentConfigConstants.java index bea1b1e..a771d2f 100644 --- a/org.intrace/src/org/intrace/shared/AgentConfigConstants.java +++ b/org.intrace/src/org/intrace/shared/AgentConfigConstants.java @@ -16,6 +16,7 @@ public class AgentConfigConstants public static final String SERVER_PORT = "SERVER_PORT"; + public static final String GZIP = "[gzip-"; //Default is not enabled (false). public static final String CLASS_REGEX = "[regex-"; public static final String EXCLUDE_CLASS_REGEX = "[exclude-regex-"; public static final String INSTRU_ENABLED = "[instru-"; @@ -37,5 +38,6 @@ public class AgentConfigConstants COMMANDS.add(VERBOSE_MODE + ""); COMMANDS.add(OPT_SERVER_PORT + ""); COMMANDS.add(CALLBACK_PORT + ""); + COMMANDS.add(GZIP + ""); } } diff --git a/org.intrace/src/org/intrace/shared/Base64.java b/org.intrace/src/org/intrace/shared/Base64.java new file mode 100644 index 0000000..4f8d7fc --- /dev/null +++ b/org.intrace/src/org/intrace/shared/Base64.java @@ -0,0 +1,2064 @@ +package org.intrace.shared; +/** + *

Encodes and decodes to and from Base64 notation.

+ *

Homepage: http://iharder.net/base64.

+ * + *

Example:

+ * + * String encoded = Base64.encode( myByteArray ); + *
+ * byte[] myByteArray = Base64.decode( encoded ); + * + *

The options parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as + * encodeBytes( bytes, options ) the options parameter can be used to indicate such + * things as first gzipping the bytes before encoding them, not inserting linefeeds, + * and encoding using the URL-safe and Ordered dialects.

+ * + *

Note, according to RFC3548, + * Section 2.1, implementations should not add line feeds unless explicitly told + * to do so. I've got Base64 set to this behavior now, although earlier versions + * broke lines by default.

+ * + *

The constants defined in Base64 can be OR-ed together to combine options, so you + * might make a call like this:

+ * + * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); + *

to compress the data before encoding it and then making the output have newline characters.

+ *

Also...

+ * String encoded = Base64.encodeBytes( crazyString.getBytes() ); + * + * + * + *

+ * Change Log: + *

+ * + * + *

+ * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** Specify that gzipped data should not be automatically gunzipped. */ + public final static int DONT_GUNZIP = 4; + + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, + * and it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) { + if( (options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + *

Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes.

+ *

This is the lowest level of the encoding methods with + * all possible parameters.

+ * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { + + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Performs Base64 encoding on the raw ByteBuffer, + * writing it to the encoded ByteBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + encoded.put(enc4); + } // end input remaining + } + + + /** + * Performs Base64 encoding on the raw ByteBuffer, + * writing it to the encoded CharBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + for( int i = 0; i < 4; i++ ){ + encoded.put( (char)(enc4[i] & 0xFF) ); + } + } // end input remaining + } + + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + *

As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException if there is an error + * @throws NullPointerException if serializedObject is null + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + throws java.io.IOException { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + *

As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * The object is not GZip-compressed before being encoded. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     * 
+ *

+ * Example: encodeObject( myObj, Base64.GZIP ) or + *

+ * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + throws java.io.IOException { + + if( serializableObject == null ){ + throw new NullPointerException( "Cannot serialize a null object." ); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + if( (options & GZIP) != 0 ){ + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream( gzos ); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream( b64os ); + } + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue){ + // Fall back to some Java default + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException if source array is null + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + *

As of v 2.3, if there is an error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes( source, off, len, NO_OPTIONS ); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + byte[] encoded = encodeBytesToBytes( source, off, len, options ); + + // Return value according to relevant encoding. + try { + return new String( encoded, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String( encoded ); + } // end catch + + } // end encodeBytes + + + + + /** + * Similar to {@link #encodeBytes(byte[])} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source ) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); + } catch( java.io.IOException ex ) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + + if( source == null ){ + throw new NullPointerException( "Cannot serialize a null array." ); + } // end if: null + + if( off < 0 ){ + throw new IllegalArgumentException( "Cannot have negative offset: " + off ); + } // end if: off < 0 + + if( len < 0 ){ + throw new IllegalArgumentException( "Cannot have length offset: " + len ); + } // end if: len < 0 + + if( off + len > source.length ){ + throw new IllegalArgumentException( + String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); + } // end if: off < 0 + + + + // Compress? + if( (options & GZIP) != 0 ) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding + if( breakLines ){ + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[ encLen ]; + + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Only resize array if we didn't guess it right. + if( e <= outBuff.length - 1 ){ + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff,0, finalOut,0,e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + *

This is the lowest level of the decoding methods with + * all possible parameters.

+ * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException if source or destination arrays are null + * @throws IllegalArgumentException if srcOffset or destOffset are invalid + * or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Source array was null." ); + } // end if + if( destination == null ){ + throw new NullPointerException( "Destination array was null." ); + } // end if + if( srcOffset < 0 || srcOffset + 3 >= source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); + } // end if + if( destOffset < 0 || destOffset +2 >= destination.length ){ + throw new IllegalArgumentException( String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); + } // end if + + + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + } + } // end decodeToBytes + + + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. Ignores GUNZIP option, if + * it's set. This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode( byte[] source ) + throws java.io.IOException { + byte[] decoded = null; +// try { + decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); +// } catch( java.io.IOException ex ) { +// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); +// } + return decoded; + } + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. Ignores GUNZIP option, if + * it's set. This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param options Can specify options such as alphabet type to use + * @return decoded data + * @throws java.io.IOException If bogus characters exist in source data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + throws java.io.IOException { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Cannot decode null source array." ); + } // end if + if( off < 0 || off + len > source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); + } // end if + + if( len == 0 ){ + return new byte[0]; + }else if( len < 4 ){ + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len ); + } // end if + + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for( i = off; i < off+len; i++ ) { // Loop through source + + sbiDecode = DECODABET[ source[i]&0xFF ]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if( sbiDecode >= WHITE_SPACE_ENC ) { + if( sbiDecode >= EQUALS_SIGN_ENC ) { + b4[ b4Posn++ ] = source[i]; // Save non-whitespace + if( b4Posn > 3 ) { // Time to decode? + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( source[i] == EQUALS_SIGN ) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new java.io.IOException( String.format( + "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @throws java.io.IOException If there is a problem + * @since 1.4 + */ + public static byte[] decode( String s ) throws java.io.IOException { + return decode( s, NO_OPTIONS ); + } + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if s is null + * @since 1.4 + */ + public static byte[] decode( String s, int options ) throws java.io.IOException { + + if( s == null ){ + throw new NullPointerException( "Input string was null." ); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) { + e.printStackTrace(); + // Just return originally-decoded bytes + } // end catch + finally { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + throws java.io.IOException, java.lang.ClassNotFoundException { + return decodeToObject(encodedObject,NO_OPTIONS,null); + } + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * If loader is not null, it will be the class loader + * used when deserializing. + * + * @param encodedObject The Base64 data to decode + * @param options Various parameters related to decoding + * @param loader Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 2.3.4 + */ + public static Object decodeToObject( + String encodedObject, int options, final ClassLoader loader ) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject, options ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream( objBytes ); + + // If no custom class loader is provided, use Java's builtin OIS. + if( loader == null ){ + ois = new java.io.ObjectInputStream( bais ); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais){ + @Override + public Class resolveClass(java.io.ObjectStreamClass streamClass) + throws java.io.IOException, ClassNotFoundException { + Class c = Class.forName(streamClass.getName(), false, loader); + if( c == null ){ + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch( java.lang.ClassNotFoundException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile( byte[] dataToEncode, String filename ) + throws java.io.IOException { + + if( dataToEncode == null ){ + throw new NullPointerException( "Data to encode was null." ); + } // end iff + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static void decodeToFile( String dataToDecode, String filename ) + throws java.io.IOException { + + Base64.OutputStream bos = null; + try{ + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param filename Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + throws java.io.IOException { + + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param filename Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + throws java.io.IOException { + + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads infile and encodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads infile and decodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: break lines at 76 characters
+         *     (only meaningful when encoding)
+         * 
+ *

+ * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) { + + super( in ); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if( position < 0 ) { + if( encode ) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) { + b3[i] = (byte)b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ){ + return -1; + } // end if: got data + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read( byte[] dest, int off, int len ) + throws java.io.IOException { + int i; + int b; + for( i = 0; i < len; i++ ) { + b = read(); + + if( b >= 0 ) { + dest[off + i] = (byte) b; + } + else if( i == 0 ) { + return -1; + } + else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         * 
+ *

+ * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) { + super( out ); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to encode. + + this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) { + this.out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to output. + + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write( byte[] theBytes, int off, int len ) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + * @throws java.io.IOException if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if( position > 0 ) { + if( encode ) { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @throws java.io.IOException if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/org.intrace/testsrc/org/intracetest/agent/AgentTest.java b/org.intrace/testsrc/org/intracetest/agent/AgentTest.java index 0fe0b5b..e3f8ae4 100644 --- a/org.intrace/testsrc/org/intracetest/agent/AgentTest.java +++ b/org.intrace/testsrc/org/intracetest/agent/AgentTest.java @@ -109,9 +109,28 @@ public class AgentTest extends TestCase assertEquals(helpResponse, expectedHelpResponse); } + /** + Intentionally, testSetting(AgentConfigConstants.GZIP, "true") does not work. + For simplicity's sake, this setting can only be configured as an intrace agent command line parameter -- [gzip-true + Note that there is no GUI checkbox to enabled Gzip. + To instead enable the client to change this, just change how ClassTransformer#getResponse() deals with a new gzip parameter. + The following simply confirms that +

+ * + * @throws Exception + */ + public void testRetrievalOfGzipSetting() throws Exception { + testGetSetting(AgentConfigConstants.GZIP, "false"); + } public void testSettings() throws Exception { // Boolean settings + + + testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); @@ -124,8 +143,12 @@ public class AgentTest extends TestCase testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); testSetting(AgentConfigConstants.VERBOSE_MODE, "true"); testSetting(AgentConfigConstants.CLASS_REGEX, ".*"); + + //Need to learn more about why these don't work here. --ETO 12/1/2013 + //testSetting(TraceConfigConstants.BRANCH, "true"); + //testSetting(TraceConfigConstants.BRANCH, "false"); } - + public void testBranchPatterns() throws Throwable { // Create and init the mock @@ -205,6 +228,7 @@ public class AgentTest extends TestCase // Setup agent testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + testSetting(AgentConfigConstants.GZIP, "false"); testSetting(AgentConfigConstants.CLASS_REGEX, "BranchPatterns"); testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); @@ -320,6 +344,7 @@ public class AgentTest extends TestCase // Setup agent testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + testSetting(AgentConfigConstants.GZIP, "false"); testSetting(AgentConfigConstants.CLASS_REGEX, "ArgumentTypes"); testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); @@ -552,6 +577,18 @@ public class AgentTest extends TestCase Map settingsResponseMap = (Map) settingsResponse; assertEquals(configValue, settingsResponseMap.get(configConstant)); } + @SuppressWarnings("unchecked") + private void testGetSetting(String configConstant, String configValue) + throws Exception + { + // Get settings + sender.sendMessage("getsettings"); + Object settingsResponse = receiver.incomingMessages.take(); + assertNotNull(settingsResponse); + assertTrue(settingsResponse instanceof Map); + Map settingsResponseMap = (Map) settingsResponse; + assertEquals(configValue, settingsResponseMap.get(configConstant)); + } @Override protected void tearDown() throws Exception diff --git a/org.intrace/testsrc/org/intracetest/agent/CompressionTest.java b/org.intrace/testsrc/org/intracetest/agent/CompressionTest.java new file mode 100644 index 0000000..19a25e5 --- /dev/null +++ b/org.intrace/testsrc/org/intracetest/agent/CompressionTest.java @@ -0,0 +1,185 @@ +package org.intracetest.agent; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.intrace.shared.Base64; +/** + +This test proves out the following API using text data from an intrace Event + +* String encoded = Base64.encode( myByteArray ); + *
+ * byte[] myByteArray = Base64.decode( encoded ); + +
+
+				Plain ASCII		 Base64		Base64+GZIP
+				-----------------------------------------
+Tiny		   |	 70			   96			 116
+               |
+Small		   |	248			  332			 256
+               |
+Big     	   |   2306		     3076		  	 904
+               |
+Huge    	   |  12591		    16788		  	1036
+
+
+
+ How small can we compress the InTrace event text for various sized InTrace events? +
    +
  • Base64 without GZIP was detrimental to small compression size in all cases
  • +
  • The 70 and 248 byte events were slightly larger with both Base64 and Base64+GZIP -- bad but a not horrible show stopper.
  • +
  • The Big event was more than 2x as small with Base64+GZIP -- good.
  • +
  • The Huge event was more than 10x as small with Base64+GZIP -- great.
  • +
  • +
+ + Conclusions: + Will not use Base64 alone -- doesn't provide any compression with these samples. + Base64+GZIP doesn't hurt much with smaller event + + + + */ +public class CompressionTest extends TestCase +{ + private static final String ORIGINAL_TINY_INTRACE_EVENT = "[18:07:53.683]:[67]:org.hsqldb.jdbc.jdbcConnection:prepareStatement: {"; + private static final String ORIGINAL_SMALL_INTRACE_EVENT = "[18:07:53.683]:[67]:org.hsqldb.jdbc.jdbcConnection:prepareStatement" + + ": Return: org.hsqldb.jdbc.jdbcPreparedStatement@4b8efa2f[sql=" + + "[INSERT INTO Event (name, description, date, location) VALUES(?, ?, ?, ?)]" + + ", parameters=[[null], [null], [null], [null]]]"; + /** + * This isn't a real event (yet)...just adding some text to assess compression for larger messages. + */ + private static final String ORIGINAL_LARGE_STACK_TRACE = "[18:07:53.683]:[67]:org.hsqldb.jdbc.jdbcConnection:prepareStatement" + + ": Return: org.hsqldb.jdbc.jdbcPreparedStatement@4b8efa2f[sql=" + + "[INSERT INTO Event (name, description, date, location) VALUES(?, ?, ?, ?)]" + + ", parameters=[[null], [null], [null], [null]]]" + + "~java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "; + + /** + * This isn't a real event at all...just adding some text to assess compression for msg with more bytes. + */ + private static final String ORIGINAL_HUGE_STACK_TRACE = "[18:07:53.683]:[67]:org.hsqldb.jdbc.jdbcConnection:prepareStatement" + + ": Return: org.hsqldb.jdbc.jdbcPreparedStatement@4b8efa2f[sql=" + + "[INSERT INTO Event (name, description, date, location) VALUES(?, ?, ?, ?)]" + + ", parameters=[[null], [null], [null], [null]]]" + + "~java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "+ + "java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "+ + "java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "+ + "java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "+ + "java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "+ + "java.lang.Thread.getStackTrace(Thread.java:1567) example.webapp.servlet.HelloWorld.doGet(HelloWorld.java:38) javax.servlet.http.HttpServlet.service(HttpServlet.java:668) javax.servlet.http.HttpServlet.service(HttpServlet.java:770) org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669) org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137) org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231) org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1072) org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:382) org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193) org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1006) org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135) org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116) org.eclipse.jetty.server.Server.handle(Server.java:365) org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:485) org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53) org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:926) org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:988) org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:635) org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235) org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72) org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264) org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608) org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543) java.lang.Thread.run(Thread.java:722) "; + + + public void testBasicCompressionAndDecompression_smallerWithGZip() throws IOException + { +// System.out.println("Original length of short data [" + ORIGINAL_SMALL_INTRACE_EVENT.length() + "]"); +// System.out.println("Original length of long data [" + ORIGINAL_LARGE_STACK_TRACE.length() + "]"); + byte[] eventData = ORIGINAL_SMALL_INTRACE_EVENT.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.GZIP); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.GZIP); + String reconstitutedString = new String(reconstitutedData); + + assertEquals("Size of original text", 248, ORIGINAL_SMALL_INTRACE_EVENT.length() ); + assertEquals("Size of compressed text", 256, compressedString.length() ); +// System.out.println("Compressed data [" + compressedString + "]"); +// System.out.println("Compressed with GZIP length - small data [" + compressedString.length() + "]"); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_SMALL_INTRACE_EVENT, reconstitutedString); + } + public void testBasicCompressionAndDecompression_largerWithGZip() throws IOException + { + byte[] eventData = ORIGINAL_LARGE_STACK_TRACE.getBytes(); + String compressedData = Base64.encodeBytes(eventData, Base64.GZIP); + //String compressedData = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + byte[] reconstitutedData = Base64.decode(compressedData, Base64.GZIP); + String reconstitutedString = new String(reconstitutedData); + + assertEquals("Size of original text", 2306, ORIGINAL_LARGE_STACK_TRACE.length() ); + assertEquals("Size of compressed text", 904, compressedData.length() ); + +// System.out.println("Compressed data [" + compressedData + "]"); +// System.out.println("Compressed with GZIP data - long data [" + compressedData.length() + "]"); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_LARGE_STACK_TRACE, reconstitutedString); + } + public void testBasicCompressionAndDecompression_smallerWithoutGZip() throws IOException + { + byte[] eventData = ORIGINAL_SMALL_INTRACE_EVENT.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.NO_OPTIONS); + String reconstitutedString = new String(reconstitutedData); + + + assertEquals("Size of original text", 248, ORIGINAL_SMALL_INTRACE_EVENT.length() ); + assertEquals("Size of compressed text", 332, compressedString.length() ); +// System.out.println("Compressed data [" + compressedString + "]"); +// System.out.println("Compressed without gzip data - small data [" + compressedString.length() + "]"); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_SMALL_INTRACE_EVENT, reconstitutedString); + } + public void testBasicCompressionAndDecompression_largerWithoutGZip() throws IOException + { + byte[] eventData = ORIGINAL_LARGE_STACK_TRACE.getBytes(); + String compressedData = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + //String compressedData = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + byte[] reconstitutedData = Base64.decode(compressedData, Base64.NO_OPTIONS); + String reconstitutedString = new String(reconstitutedData); + + assertEquals("Size of original text", 2306, ORIGINAL_LARGE_STACK_TRACE.length() ); + assertEquals("Size of compressed text", 3076, compressedData.length() ); + +// System.out.println("Compressed data [" + compressedData + "]"); +// System.out.println("Compressed without gzip - large data [" + compressedData.length() + "]"); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_LARGE_STACK_TRACE, reconstitutedString); + } + + public void testBasicCompressionAndDecompression_tinyWithGZip() throws IOException + { + byte[] eventData = ORIGINAL_TINY_INTRACE_EVENT.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.GZIP); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.GZIP); + String reconstitutedString = new String(reconstitutedData); + + + assertEquals("Size of original text", 70, ORIGINAL_TINY_INTRACE_EVENT.length() ); + assertEquals("Size of compressed text", 116, compressedString.length() ); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_TINY_INTRACE_EVENT, reconstitutedString); + } + public void testBasicCompressionAndDecompression_tinyWithoutGZip() throws IOException + { + byte[] eventData = ORIGINAL_TINY_INTRACE_EVENT.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.NO_OPTIONS); + String reconstitutedString = new String(reconstitutedData); + + + assertEquals("Size of original text", 70, ORIGINAL_TINY_INTRACE_EVENT.length() ); + assertEquals("Size of compressed text", 96, compressedString.length() ); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_TINY_INTRACE_EVENT, reconstitutedString); + } + public void testBasicCompressionAndDecompression_hugeWithGZip() throws IOException + { + byte[] eventData = ORIGINAL_HUGE_STACK_TRACE.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.GZIP); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.GZIP); + String reconstitutedString = new String(reconstitutedData); + + + assertEquals("Size of original text", 12591, ORIGINAL_HUGE_STACK_TRACE.length() ); + assertEquals("Size of compressed text", 1036, compressedString.length() ); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_HUGE_STACK_TRACE, reconstitutedString); + } + public void testBasicCompressionAndDecompression_hugeWithoutGZip() throws IOException + { + byte[] eventData = ORIGINAL_HUGE_STACK_TRACE.getBytes(); + String compressedString = Base64.encodeBytes(eventData, Base64.NO_OPTIONS); + byte[] reconstitutedData = Base64.decode(compressedString, Base64.NO_OPTIONS); + String reconstitutedString = new String(reconstitutedData); + + + assertEquals("Size of original text", 12591, ORIGINAL_HUGE_STACK_TRACE.length() ); + assertEquals("Size of compressed text", 16788, compressedString.length() ); + assertEquals("Unable to compress and decompress intrace event data", ORIGINAL_HUGE_STACK_TRACE, reconstitutedString); + } +} diff --git a/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java b/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java new file mode 100644 index 0000000..26d769a --- /dev/null +++ b/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java @@ -0,0 +1,48 @@ +package org.intracetest.agent; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.intrace.agent.AgentSettings; +import org.intrace.output.trace.TraceSettings; +import org.intrace.shared.AgentConfigConstants; +import org.intrace.shared.TraceConfigConstants; + +public class TraceSettingsTest extends TestCase +{ + public void testAgentSettings() + { + TraceSettings ts = new TraceSettings( + TraceConfigConstants.ARG + + "true" + + TraceConfigConstants.ARRAYS + + "true" + + TraceConfigConstants.ENTRY_EXIT + + "true" + + TraceConfigConstants.BRANCH + + "true" + ); + + assertTrue(ts.isArgTraceEnabled()); + assertTrue(ts.isEntryExitTraceEnabled()); + assertTrue(ts.isBranchTraceEnabled()); + assertTrue(ts.isTruncateArraysEnabled()); + + ts = new TraceSettings(ts); + assertTrue(ts.isArgTraceEnabled()); + assertTrue(ts.isEntryExitTraceEnabled()); + assertTrue(ts.isBranchTraceEnabled()); + assertTrue(ts.isTruncateArraysEnabled()); + + String toString = ts.toString(); + assertNotNull(toString); + + Map settingsMap = ts.getSettingsMap(); + assertEquals(settingsMap.get(TraceConfigConstants.ARG), "true"); + assertEquals(settingsMap.get(TraceConfigConstants.ARRAYS), "true"); + assertEquals(settingsMap.get(TraceConfigConstants.BRANCH), "true"); + assertEquals(settingsMap.get(TraceConfigConstants.ENTRY_EXIT), "true"); + } + +} From 560fa99eed7eb552348cad41a225c4970c1d1e45 Mon Sep 17 00:00:00 2001 From: Erik Ostermueller Date: Sun, 8 Dec 2013 09:58:24 -0600 Subject: [PATCH 2/2] New UI checkbox enables user to capture stack trace of every 'exit' trace event. This is useful for troubleshooting like this: http://johnsrandommusings.tumblr.com/post/33371606685/javadbb --- org.intrace/lib/intrace-agent.jar | Bin 386624 -> 387403 bytes .../client/gui/helper/ClientStrings.java | 1 + .../intrace/client/gui/helper/InTraceUI.java | 20 ++++++ .../client/gui/helper/ParsedSettingsData.java | 11 +++ .../intrace/output/trace/TraceHandler.java | 52 +++++++++++++-- .../intrace/output/trace/TraceSettings.java | 25 +++++++ .../intrace/shared/AgentConfigConstants.java | 2 + .../intrace/shared/TraceConfigConstants.java | 2 + .../org/intracetest/agent/StackTraceTest.java | 63 ++++++++++++++++++ .../intracetest/agent/TraceSettingsTest.java | 5 ++ 10 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 org.intrace/testsrc/org/intracetest/agent/StackTraceTest.java diff --git a/org.intrace/lib/intrace-agent.jar b/org.intrace/lib/intrace-agent.jar index 19af344e539fb7ecf802266ad42a2b8bbdeef064..2b7f669b8ab4bc757237ac294d14e109a75d3972 100644 GIT binary patch delta 21715 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 sb.append(STACK_ELE_DELIM); //Just like Arrays.toString(), place a comma between each stack trace ele. + sb.append(ste.toString()); + } + } + + return sb.toString(); + } @Override public void exit(String className, String methodName, int lineNo) { if (entryExitTrace) { - writeTraceOutput(className + ":" + methodName + ": }" + - (lineNo >= 0 ? ":" + lineNo : "")); + + if (exitStackTrace) { + writeTraceOutput(className + ":" + methodName + ": }" + + (lineNo >= 0 ? ":" + lineNo : "") + + STACK_TRACE_DELIM + getStackTrace() ); + } else { + writeTraceOutput(className + ":" + methodName + ": }" + + (lineNo >= 0 ? ":" + lineNo : "") ); + } } } diff --git a/org.intrace/src/org/intrace/output/trace/TraceSettings.java b/org.intrace/src/org/intrace/output/trace/TraceSettings.java index 3972648..3ef29f1 100644 --- a/org.intrace/src/org/intrace/output/trace/TraceSettings.java +++ b/org.intrace/src/org/intrace/output/trace/TraceSettings.java @@ -17,6 +17,16 @@ public class TraceSettings private boolean branchTraceEnabled = false; private boolean argTraceEnabled = true; private boolean truncateArraysEnabled = true; + + /** + * If true, append the 'current' stack trace to the text of the exit trace event. + * This is helpful for discovering who is invoking a particular line of code. + * Here is an example of the trace output: + *
+   * [07:53:15.509]:[1]:example.FirstTraceExample:intArrayMethod: }:70~java.lang.Thread.getStackTrace(Thread.java:1567),example.FirstTraceExample.intArrayMethod(FirstTraceExample.java:70),example.FirstTraceExample.workMethod(FirstTraceExample.java:38),example.FirstTraceExample.otherMain(FirstTraceExample.java:29),example.FirstTraceExample.main(FirstTraceExample.java:16)
+   * 
+ */ + private boolean exitStackTrace = false; public TraceSettings(TraceSettings oldSettings) { @@ -24,6 +34,7 @@ public class TraceSettings branchTraceEnabled = oldSettings.branchTraceEnabled; argTraceEnabled = oldSettings.argTraceEnabled; truncateArraysEnabled = oldSettings.truncateArraysEnabled; + exitStackTrace = oldSettings.exitStackTrace; } public TraceSettings(String args) @@ -74,6 +85,14 @@ public class TraceSettings { truncateArraysEnabled = false; } + else if (arg.equals(TraceConfigConstants.EXIT_STACK_TRACE + "false")) + { + exitStackTrace = false; + } + else if (arg.equals(TraceConfigConstants.EXIT_STACK_TRACE + "true")) + { + exitStackTrace = true; + } } public boolean isEntryExitTraceEnabled() @@ -95,6 +114,9 @@ public class TraceSettings { return truncateArraysEnabled; } + public boolean isExitStackTraceEnabled() { + return exitStackTrace; + } public Map getSettingsMap() { @@ -107,6 +129,9 @@ public class TraceSettings .put(TraceConfigConstants.ARG, Boolean.toString(argTraceEnabled)); settingsMap .put(TraceConfigConstants.ARRAYS, Boolean.toString(truncateArraysEnabled)); + + settingsMap.put(TraceConfigConstants.EXIT_STACK_TRACE, + Boolean.toString(exitStackTrace)); return settingsMap; } } \ No newline at end of file diff --git a/org.intrace/src/org/intrace/shared/AgentConfigConstants.java b/org.intrace/src/org/intrace/shared/AgentConfigConstants.java index a771d2f..b3fe9b2 100644 --- a/org.intrace/src/org/intrace/shared/AgentConfigConstants.java +++ b/org.intrace/src/org/intrace/shared/AgentConfigConstants.java @@ -24,6 +24,7 @@ public class AgentConfigConstants public static final String VERBOSE_MODE = "[verbose-"; public static final String OPT_SERVER_PORT = "[serverport-"; public static final String CALLBACK_PORT = "[callbackport-"; + public static final String EXIT_STACK_TRACE = "[exit-stack-trace-"; public static final String START_WAIT = "[startwait"; public static final String START_ACTIVATE = "[startactivate"; @@ -39,5 +40,6 @@ public class AgentConfigConstants COMMANDS.add(OPT_SERVER_PORT + ""); COMMANDS.add(CALLBACK_PORT + ""); COMMANDS.add(GZIP + ""); + COMMANDS.add(EXIT_STACK_TRACE + ""); } } diff --git a/org.intrace/src/org/intrace/shared/TraceConfigConstants.java b/org.intrace/src/org/intrace/shared/TraceConfigConstants.java index 045c5b8..55c70c4 100644 --- a/org.intrace/src/org/intrace/shared/TraceConfigConstants.java +++ b/org.intrace/src/org/intrace/shared/TraceConfigConstants.java @@ -12,6 +12,7 @@ public class TraceConfigConstants public static final String STD_OUT = "[out-stdout-"; public static final String FILE_OUT = "[out-file-"; public static final String NET_OUT = "[out-network-"; + public static final String EXIT_STACK_TRACE = "[exit-stack-trace-"; public static final Set COMMANDS = new HashSet(); static { @@ -22,5 +23,6 @@ public class TraceConfigConstants COMMANDS.add(STD_OUT + ""); COMMANDS.add(FILE_OUT + ""); COMMANDS.add(NET_OUT + ""); + COMMANDS.add(EXIT_STACK_TRACE + ""); } } diff --git a/org.intrace/testsrc/org/intracetest/agent/StackTraceTest.java b/org.intrace/testsrc/org/intracetest/agent/StackTraceTest.java new file mode 100644 index 0000000..a8faa33 --- /dev/null +++ b/org.intrace/testsrc/org/intracetest/agent/StackTraceTest.java @@ -0,0 +1,63 @@ +package org.intracetest.agent; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.intrace.agent.AgentSettings; +import org.intrace.output.trace.TraceHandler; +import org.intrace.output.trace.TraceSettings; +import org.intrace.shared.AgentConfigConstants; +import org.intrace.shared.TraceConfigConstants; + +public class StackTraceTest extends TestCase +{ + private static final String MY_PACKAGE_AND_CLASS = "org.intracetest.agent.StackTraceTest"; + private String m_stackTrace; + /** Here is the raw stack trace that we want to validate: + *
+org.intracetest.agent.StackTraceTest.c(StackTraceTest.java:57),org.intracetest.agent.StackTraceTest.b(StackTraceTest.java:54),org.intracetest.agent.StackTraceTest.a(StackTraceTest.java:51),org.intracetest.agent.StackTraceTest.testStackTrace(StackTraceTest.java:27),sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method),sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57),sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43),java.lang.reflect.Method.invoke(Method.java:601),junit.framework.TestCase.runTest(TestCase.java:168),junit.framework.TestCase.runBare(TestCase.java:134),junit.framework.TestResult$1.protect(TestResult.java:110),junit.framework.TestResult.runProtected(TestResult.java:128),junit.framework.TestResult.run(TestResult.java:113),junit.framework.TestCase.run(TestCase.java:124),junit.framework.TestSuite.runTest(TestSuite.java:243),junit.framework.TestSuite.run(TestSuite.java:238),org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83),org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50),org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38),org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467),org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683),org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390),org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
+   * 
+ * Among other things, this test validates that the following are removed from the stack trace: + *
    + *
  • java.lang.Thread.getStackTrace
  • + *
  • all org.intrace. activity
  • + *
+ * + */ +public void testStackTrace() + { + a(); + String[] parts = m_stackTrace.split(","); + //System.out.println(m_stackTrace); + //JUnit has a thick call stack -- when I checked, there were 23 stack trace elements. + //Application developers only care about the ones on the very top. + assertTrue("Didn't not find the right number of StackTraceElements", parts.length > 4); + validateStackTraceElement("org.intracetest.agent.StackTraceTest.c", parts[0]); + validateStackTraceElement("org.intracetest.agent.StackTraceTest.b", parts[1]); + validateStackTraceElement("org.intracetest.agent.StackTraceTest.a", parts[2]); + validateStackTraceElement("org.intracetest.agent.StackTraceTest.testStackTrace", parts[3]); + //validateStackTraceElement("sun.reflect.NativeMethodAccessorImpl.invoke0", parts[4]); + } + private void validateStackTraceElement(String expectedPackageAndClassAndMethod, String actual) { + String[] partsOfStackTraceElement = actual.split("[\\(:\\)]"); + assertEquals("The package and class and method name were not found in the right place", + expectedPackageAndClassAndMethod, + partsOfStackTraceElement[0]); + + assertEquals("source file not found in the right place", + "StackTraceTest.java", + partsOfStackTraceElement[1]); + + } +private void a() { + b(); + } + private void b() { + c(); + } + private void c() { + m_stackTrace = TraceHandler.INSTANCE.getStackTrace(); + } + +} diff --git a/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java b/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java index 26d769a..fe34af4 100644 --- a/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java +++ b/org.intrace/testsrc/org/intracetest/agent/TraceSettingsTest.java @@ -22,17 +22,21 @@ public class TraceSettingsTest extends TestCase + "true" + TraceConfigConstants.BRANCH + "true" + + TraceConfigConstants.EXIT_STACK_TRACE + + "true" ); assertTrue(ts.isArgTraceEnabled()); assertTrue(ts.isEntryExitTraceEnabled()); assertTrue(ts.isBranchTraceEnabled()); + assertTrue(ts.isExitStackTraceEnabled()); assertTrue(ts.isTruncateArraysEnabled()); ts = new TraceSettings(ts); assertTrue(ts.isArgTraceEnabled()); assertTrue(ts.isEntryExitTraceEnabled()); assertTrue(ts.isBranchTraceEnabled()); + assertTrue(ts.isExitStackTraceEnabled()); assertTrue(ts.isTruncateArraysEnabled()); String toString = ts.toString(); @@ -42,6 +46,7 @@ public class TraceSettingsTest extends TestCase assertEquals(settingsMap.get(TraceConfigConstants.ARG), "true"); assertEquals(settingsMap.get(TraceConfigConstants.ARRAYS), "true"); assertEquals(settingsMap.get(TraceConfigConstants.BRANCH), "true"); + assertEquals(settingsMap.get(TraceConfigConstants.EXIT_STACK_TRACE),"true"); assertEquals(settingsMap.get(TraceConfigConstants.ENTRY_EXIT), "true"); }