/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ocpsoft.rewrite.servlet.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * Utility for building URL query strings.
 *
 * @deprecated Use {@link AddressBuilder} instead. May be removed in subsequent releases.
 *
 * @author Lincoln Baxter, III <lincoln@ocpsoft.com>
 */
@Deprecated
public class QueryStringBuilder
{
   private final static Logger log = Logger.getLogger(QueryStringBuilder.class);

   private final Map<String, List<String>> parameters = new LinkedHashMap<String, List<String>>();

   /**
    * Return a new empty instance of {@link QueryStringBuilder}
    */
   public static QueryStringBuilder createNew()
   {
      return new QueryStringBuilder();
   }

   /**
    * Build a query string from the given URL. If a '?' character is encountered in the URL, the any characters up to
    * and including the first '?' will be ignored. This method assumes that the given parameters have already been URL
    * encoded.
    */
   public static QueryStringBuilder createFromEncoded(final String parameters)
   {
      QueryStringBuilder queryString = new QueryStringBuilder();
      queryString.addParameters(parameters);
      return queryString;
   }

   /**
    * Build a query string from the given map of name=value pairs. For parameters with more than one value, each value
    * will be appended using the same name.
    */
   public static QueryStringBuilder createFromArrays(final Map<String, String[]> params)
   {
      return QueryStringBuilder.createNew().addParameterArrays(params);
   }

   /**
    * Build a query string from the given map of name=value pairs. For parameters with more than one value, each value
    * will be appended using the same name.
    */
   public static QueryStringBuilder createFromLists(final Map<String, List<String>> params)
   {
      return QueryStringBuilder.createNew().addParameterLists(params);
   }

   /**
    * Get the query string portion of the given URL. If no query string separator character '?' can be found, return the
    * string unchanged.
    */
   public static String extractQuery(String url)
   {
      if (url != null)
      {
         int index = url.indexOf('?');
         if (index >= 0)
         {
            url = url.substring(index + 1);
         }
      }
      return url;
   }

   /**
    * Add parameters from the given URL. If a '?' character is encountered in the URL, the any characters up to and
    * including the first '?' will be ignored. If a parameter already exists, append new values to the existing list of
    * values for that parameter.
    *

    * Note: This method assumes that the given string is already URL encoded.
    */
   public QueryStringBuilder addParameters(final String url)
   {
      String temp = extractQuery(url);
      if (temp != null)
      {
         temp = decodeHTMLAmpersands(temp);
         int index = 0;
         while ((index = temp.indexOf('&')) >= 0 || !temp.isEmpty())
         {
            String pair = temp;
            if (index >= 0)
            {
               pair = temp.substring(0, index);
               temp = temp.substring(index);
               if (!temp.isEmpty())
                  temp = temp.substring(1);
            }
            else
               temp = "";

            String name;
            String value;
            int pos = pair.indexOf('=');
            // for "n=", the value is "", for "n", the value is null
            if (pos == -1)
            {
               name = pair;
               value = null;
            }
            else
            {
               name = pair.substring(0, pos);
               value = pair.substring(pos + 1, pair.length());
            }

            List<String> list = parameters.get(name);
            if (list == null)
            {
               list = new ArrayList<String>();
               parameters.put(name, list);
            }
            list.add(value);
         }
      }
      return this;
   }

   private String decodeHTMLAmpersands(String url)
   {
      if (url != null)
      {
         int index = 0;
         while ((index = url.indexOf("&")) >= 0)
         {
            url = url.substring(0, index + 1) + url.substring(index + 5);
         }
      }
      return url;
   }

   /**
    * Add a single parameter with the given values.
    */
   public void addParameter(final String name, final String... values)
   {
      Map<String, String[]> parameter = new LinkedHashMap<String, String[]>();
      parameter.put(name, values);
      addParameterArrays(parameter);
   }

   /**
    * Add parameters from the given map of name=value pairs. For parameters with more than one value, each value will be
    * appended using the same name. If a parameter already exists, append new values to the existing list of values for
    * that parameter.
    */
   public QueryStringBuilder addParameterArrays(final Map<String, String[]> params)
   {
      if (params != null)
      {
         for (Entry<String, String[]> entry : params.entrySet())
         {
            List<String> values = null;
            if (entry.getValue() != null)
            {
               List<String> temp = new ArrayList<String>();
               if (entry.getValue() != null)
               {
                  for (String value : entry.getValue()) {
                     temp.add(value);
                  }
               }
               values = temp;
            }
            parameters.put(entry.getKey(), values);
         }
      }
      return this;
   }

   /**
    * Add parameters from the given map of name=value pairs. For parameters with more than one value, each value will be
    * appended using the same name. If a parameter already exists, append new values to the existing list of values for
    * that parameter.
    */
   public QueryStringBuilder addParameterLists(final Map<String, List<String>> params)
   {
      if (params != null)
      {
         for (Entry<String, List<String>> entry : params.entrySet())
         {
            List<String> values = null;
            if (entry.getValue() != null)
            {
               values = new ArrayList<String>(entry.getValue());
            }
            parameters.put(entry.getKey(), values);
         }
      }
      return this;
   }

   /**
    * Return a new {@link QueryStringBuilder} instance having called {@link URLDecoder#decode(String, String)} on each
    * name=value pair.
    */
   public QueryStringBuilder decode()
   {
      return new QueryStringBuilder().addParameterLists(getParameterMap(new QSDecoder()));
   }

   /**
    * Return a new {@link QueryStringBuilder} instance having called {@link URLEncoder#encode(String, String)} on each
    * name=value pair.
    */
   public QueryStringBuilder encode()
   {
      return new QueryStringBuilder().addParameterLists(getParameterMap(new QSEncoder()));
   }

   /**
    * Get the first value of given parameter name.
    *
    * @return The value of the parameter, null if the parameter does not exist, or "" if the parameter exists but has no
    *         values.
    */
   public String getParameter(final String name)
   {
      List<String> values = parameters.get(name);
      if (values == null)
      {
         return null;
      }

      if (values.size() == 0)
      {
         return "";
      }

      return values.get(0);
   }

   /**
    * Get the name, values[] map representing this query string.
    */
   public Map<String, List<String>> getParameterMap()
   {
      return getParameterMap(new NullEncodingHandler());
   }

   private Map<String, List<String>> getParameterMap(final EncodingHandler handler)
   {
      Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
      for (Map.Entry<String, List<String>> entry : parameters.entrySet())
      {
         String key = handler.encode(entry.getKey());
         List<String> values = new ArrayList<String>();
         for (String value : entry.getValue()) {
            if (value != null) {
               values.add(handler.encode(value));
            }
         }
         map.put(key, new ArrayList<String>(values));
      }
      return map;
   }

   /**
    * Get set of parameter names currently stored in this query string.
    */
   public Set<String> getParameterNames()
   {
      return new LinkedHashSet<String>(parameters.keySet());
   }

   /**
    * Get the array of values for a given parameter name.
    *
    * @return The values of the parameter, null if the parameter does not exist.
    */
   public String[] getParameterValues(final String name)
   {
      List<String> values = parameters.get(name);
      if (values == null)
      {
         return null;
      }

      return values.toArray(new String[values.size()]);
   }

   /*
    * Utility Methods
    */
   /**
    * Return true if this query string currently contains no parameters.
    */
   public boolean isEmpty()
   {
      return parameters.isEmpty();
   }

   public List<String> removeParameter(final String string)
   {
      return parameters.remove(string);
   }

   /**
    * Convert the current parameters to a valid query string, including the leading '?' character.
    *

    *
    * For example, a {@link QueryStringBuilder} with the values [key=>value, name=>value1,value2,value3] will become:
    *
    *

    *
    * ?key=value&name=value1&name=value2&name=value3
    *

    *
    * @return If parameters exist, return a valid query string with leading '?' character. If no parameters exist,
    *         return an empty string.
    */
   public String toQueryString()
   {
      StringBuilder result = new StringBuilder();

      if ((null != parameters) && !parameters.isEmpty())
      {
         result.append("?");
         Iterator<Entry<String, List<String>>> iterator = parameters.entrySet().iterator();
         while (iterator.hasNext())
         {
            Entry<String, List<String>> entry = iterator.next();
            String key = entry.getKey();
            List<String> values = entry.getValue();

            if ((key != null) && !"".equals(key))
            {
               result.append(key);

               if ((values != null) && !values.isEmpty())
               {
                  for (int i = 0; i < values.size(); i++)
                  {
                     String value = values.get(i);
                     if ((value != null) && !"".equals(value))
                     {
                        result.append("=" + value);
                     }
                     else if ((value != null) && "".equals(value))
                     {
                        result.append("=");
                     }

                     if (i < (values.size() - 1))
                     {
                        result.append("&" + key);
                     }
                  }
               }
            }

            if (iterator.hasNext())
            {
               result.append("&");
            }
         }
      }

      return result.toString();
   }

   @Override
   public String toString()
   {
      return toQueryString();
   }

   private interface EncodingHandler
   {
      public String encode(String value);
   }

   private class NullEncodingHandler implements EncodingHandler
   {
      @Override
      public String encode(final String value)
      {
         return value;
      }
   }

   private class QSDecoder implements EncodingHandler
   {
      @Override
      public String encode(final String value)
      {
         try {
            return URLDecoder.decode(value, "UTF-8");
         }
         catch (UnsupportedEncodingException e) {
            throw new RewriteException("Encoding type UTF-8 is not supported by this JVM.", e);
         }
         catch (IllegalArgumentException e)
         {
            log.warn("Could not decode query parameter: " + value);
            return value;
         }
      }
   }

   private class QSEncoder implements EncodingHandler
   {
      @Override
      public String encode(final String value)
      {
         try {
            return URLEncoder.encode(value, "UTF-8");
         }
         catch (UnsupportedEncodingException e) {
            throw new RewriteException("Encoding type UTF-8 is not supported by this JVM.", e);
         }
         catch (IllegalArgumentException e)
         {
            log.warn("Could not encode query parameter: " + value);
            return value;
         }
      }
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.config.encodequery;

import java.io.IOException;

/**
 * Encodes and decodes to and from Base64 notation.
 *
 *

 * Change Log:
 *

 *

 *
v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading
 * and writing to and from files.

 *
v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like
 * EBCDIC).

 *
v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.

 *
v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner.
 * The code now detects when data that's being decoded is gzip-compressed and will decompress it automatically.
 * Generally things are cleaner. You'll probably have to change some method calls that you were making to support the
 * new options format (<tt>int</tt>s that you "OR" together).

 *
v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using
 * <tt>decode( String s, boolean gzipCompressed )</tt>. Added the ability to "suspend" encoding in the Output Stream so
 * you can turn on and off the encoding if you need to embed base64 data in an otherwise "normal" stream (like an XML
 * file).

 *
v1.5 - Output stream pases on flush() command but doesn't do anything itself. This helps when using GZIP streams.
 * Added the ability to GZip-compress objects before encoding them.

 *
v1.4 - Added helper methods to read/write files.

 *
v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.

 *
v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if
 * not completely full, was not returned.

 *
v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.

 *
v1.3.3 - Fixed I/O streams which were totally messed up.

 *

 *
 *

 * 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.1
 */
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. */
   public final static int ENCODE = 1;

   /** Specify decoding. */
   public final static int DECODE = 0;

   /** Specify that data should be gzip-compressed. */
   public final static int GZIP = 2;

   /** Don't break lines when encoding (violates strict Base64 specification) */
   public final static int DONT_BREAK_LINES = 8;

   /* ********  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 = "UTF-8";

   /** The 64 valid Base64 values. */
   private final static byte[] ALPHABET;
   private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
   { (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) '/' };

   /** Determine which ALPHABET to use. */
   static
   {
      byte[] __bytes;
      try
      {
         __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING);
      } // end try
      catch (java.io.UnsupportedEncodingException use)
      {
         __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
      } // end catch
      ALPHABET = __bytes;
   } // end static

   /**
    * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other
    * meaning.
    **/
   private final static byte[] 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 // Decimal 123 - 126
            /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 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 */
   };

   // I think I end up not using the BAD_ENCODING indicator.
   // private final static byte BAD_ENCODING = -9; // Indicates error in encoding
   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

   /** 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 <var>threeBytes</var> and returns a four-byte array in Base64
    * notation. The actual number of significant bytes in your array is given by <var>numSigBytes</var>. The array
    * <var>threeBytes</var> needs only be as big as <var>numSigBytes</var>. Code can reuse a byte array by passing a
    * four-byte array as <var>b4</var>.
    *
    * @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(final byte[] b4, final byte[] threeBytes, final int numSigBytes)
   {
      encode3to4(threeBytes, 0, numSigBytes, b4, 0);
      return b4;
   } // end encode3to4

   /**
    * Encodes up to three bytes of the array <var>source</var> and writes the resulting four Base64 bytes to
    * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by
    * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays are
    * large enough to accomodate <var>srcOffset</var> + 3 for the <var>source</var> array or <var>destOffset</var> + 4
    * for the <var>destination</var> array. The actual number of significant bytes in your array is given by
    * <var>numSigBytes</var>.
    *
    * @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 <var>destination</var> array
    * @since 1.3
    */
   private static byte[] encode3to4(final byte[] source, final int srcOffset, final int numSigBytes,
            final byte[] destination, final int destOffset)
   {
      // 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

   /**
    * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be
    * serialized or there is another error, the method will return <tt>null</tt>. The object is not GZip-compressed
    * before being encoded.
    *
    * @param serializableObject The object to encode
    * @return The Base64-encoded object
    * @since 1.4
    */
   public static String encodeObject(final java.io.Serializable serializableObject)
   {
      return encodeObject(serializableObject, NO_OPTIONS);
   } // end encodeObject

   /**
    * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be
    * serialized or there is another error, the method will return <tt>null</tt>.
    *

    * Valid options:
    *
    *

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

    *

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

    * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )
    *
    * @param serializableObject The object to encode
    * @param options Specified options
    * @return The Base64-encoded object
    * @see Base64#GZIP
    * @see Base64#DONT_BREAK_LINES
    * @since 2.0
    */
   public static String encodeObject(final java.io.Serializable serializableObject, final int options)
   {
      // Streams
      java.io.ByteArrayOutputStream baos = null;
      java.io.OutputStream b64os = null;
      java.io.ObjectOutputStream oos = null;
      java.util.zip.GZIPOutputStream gzos = null;

      // Isolate options
      int gzip = (options & GZIP);
      int dontBreakLines = (options & DONT_BREAK_LINES);

      try
      {
         // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
         baos = new java.io.ByteArrayOutputStream();
         b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);

         // GZip?
         if (gzip == GZIP)
         {
            gzos = new java.util.zip.GZIPOutputStream(b64os);
            oos = new java.io.ObjectOutputStream(gzos);
         } // end if: gzip
         else
            oos = new java.io.ObjectOutputStream(b64os);

         oos.writeObject(serializableObject);
      } // end try
      catch (java.io.IOException e)
      {
         e.printStackTrace();
         return null;
      } // 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)
      {
         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 encode bytes
    * @since 1.4
    */
   public static String encodeBytes(final byte[] source)
   {
      return encodeBytes(source, 0, source.length, NO_OPTIONS);
   } // end encodeBytes

   /**
    * Encodes a byte array into Base64 notation.
    *

    * Valid options:
    *
    *

    *   GZIP: gzip-compresses object before encoding it.
    *   DONT_BREAK_LINES: don't 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.DONT_BREAK_LINES )
    *
    *
    * @param source The data to convert
    * @param options Specified options
    * @return the encoded bytes
    * @see Base64#GZIP
    * @see Base64#DONT_BREAK_LINES
    * @since 2.0
    */
   public static String encodeBytes(final byte[] source, final int options)
   {
      return encodeBytes(source, 0, source.length, options);
   } // end encodeBytes

   /**
    * Encodes a byte array into Base64 notation. Does not GZip-compress data.
    *
    * @param source The data to convert
    * @param off Offset in array where conversion should begin
    * @param len Length of data to convert
    * @return the text node
    * @since 1.4
    */
   public static String encodeBytes(final byte[] source, final int off, final int len)
   {
      return encodeBytes(source, off, len, NO_OPTIONS);
   } // end encodeBytes

   /**
    * Encodes a byte array into Base64 notation.
    *

    * Valid options:
    *
    *

    *   GZIP: gzip-compresses object before encoding it.
    *   DONT_BREAK_LINES: don't 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.DONT_BREAK_LINES )
    *
    *
    * @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
    * @see Base64#GZIP
    * @see Base64#DONT_BREAK_LINES
    * @return the text node
    * @since 2.0
    */
   public static String encodeBytes(final byte[] source, final int off, final int len, final int options)
   {
      // Isolate options
      int dontBreakLines = (options & DONT_BREAK_LINES);
      int gzip = (options & GZIP);

      // Compress?
      if (gzip == GZIP)
      {
         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 | dontBreakLines);
            gzos = new java.util.zip.GZIPOutputStream(b64os);

            gzos.write(source, off, len);
            gzos.close();
         } // end try
         catch (java.io.IOException e)
         {
            e.printStackTrace();
            return null;
         } // end catch
         finally
         {
            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)
         {
            return new String(baos.toByteArray());
         } // end catch
      } // end if: compress

      // Else, don't compress. Better not to use streams at all then.
      else
      {
         // Convert option to boolean in way that code likes it.
         boolean breakLines = dontBreakLines == 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
         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);

            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);
            e += 4;
         } // end if: some padding needed

         // Return value according to relevant encoding.
         try
         {
            return new String(outBuff, 0, e, PREFERRED_ENCODING);
         } // end try
         catch (java.io.UnsupportedEncodingException uue)
         {
            return new String(outBuff, 0, e);
         } // end catch

      } // end else: don't compress

   } // end encodeBytes

   /* ********  D E C O D I N G   M E T H O D S  ******** */

   /**
    * Decodes four bytes from array <var>source</var> and writes the resulting bytes (up to three of them) to
    * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by
    * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays are
    * large enough to accomodate <var>srcOffset</var> + 4 for the <var>source</var> array or <var>destOffset</var> + 3
    * for the <var>destination</var> array. This method returns the actual number of bytes that were converted from the
    * Base64 encoding.
    *
    *
    * @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
    * @return the number of decoded bytes converted
    * @since 1.3
    */
   private static int decode4to3(final byte[] source, final int srcOffset, final byte[] destination,
            final int destOffset)
   {
      // 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
      {
         try
         {
            // 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;
         }
         catch (Exception e)
         {
            return -1;
         } // end catch
      }
   } // end decodeToBytes

   /**
    * Very low-level access to decoding ASCII characters in the form of a byte array. Does not support automatically
    * gunzipping or any other "fancy" features.
    *
    * @param source The Base64 encoded data
    * @param off The offset of where to begin decoding
    * @param len The length of characters to decode
    * @return decoded data
    * @since 1.3
    */
   public static byte[] decode(final byte[] source, final int off, final int len)
   {
      int len34 = (len * 3) / 4;
      byte[] outBuff = new byte[len34]; // Upper limit on size of output
      int outBuffPosn = 0;

      byte[] b4 = new byte[4];
      int b4Posn = 0;
      int i = 0;
      byte sbiCrop = 0;
      byte sbiDecode = 0;
      for (i = off; i < (off + len); i++)
      {
         sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
         sbiDecode = DECODABET[sbiCrop];

         if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
         {
            if (sbiDecode >= EQUALS_SIGN_ENC)
            {
               b4[b4Posn++] = sbiCrop;
               if (b4Posn > 3)
               {
                  outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
                  b4Posn = 0;

                  // If that was the equals sign, break out of 'for' loop
                  if (sbiCrop == EQUALS_SIGN)
                     break;
               } // end if: quartet built

            } // end if: equals sign or better

         } // end if: white space, equals sign or better
         else
         {
            throw new IllegalStateException("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)");
         } // 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
    * @since 1.4
    */
   public static byte[] decode(final String s)
   {
      byte[] bytes;
      try
      {
         bytes = s.getBytes(PREFERRED_ENCODING);
      } // end try
      catch (java.io.UnsupportedEncodingException uee)
      {
         bytes = s.getBytes();
      } // end catch
        // </change>

      // Decode
      bytes = decode(bytes, 0, bytes.length);

      // Check to see if it's gzip-compressed
      // GZIP Magic Two-Byte Number: 0x8b1f (35615)
      if ((bytes != null) && (bytes.length >= 4))
      {

         int head = (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)
            {
               // 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 <tt>null</tt> if there was an error.
    *
    * @param encodedObject The Base64 data to decode
    * @return The decoded and deserialized object
    * @since 1.5
    */
   public static Object decodeToObject(final String encodedObject)
   {
      // Decode and gunzip if necessary
      byte[] objBytes = decode(encodedObject);

      java.io.ByteArrayInputStream bais = null;
      java.io.ObjectInputStream ois = null;
      Object obj = null;

      try
      {
         bais = new java.io.ByteArrayInputStream(objBytes);
         ois = new java.io.ObjectInputStream(bais);

         obj = ois.readObject();
      } // end try
      catch (java.io.IOException e)
      {
         e.printStackTrace();
         obj = null;
      } // end catch
      catch (java.lang.ClassNotFoundException e)
      {
         e.printStackTrace();
         obj = null;
      } // 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.
    *
    * @param dataToEncode byte array of data to encode in base64 form
    * @param filename Filename for saving encoded data
    * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
    *
    * @since 2.1
    */
   public static boolean encodeToFile(final byte[] dataToEncode, final String filename)
   {
      boolean success = false;
      Base64.OutputStream bos = null;
      try
      {
         bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
         bos.write(dataToEncode);
         success = true;
      } // end try
      catch (java.io.IOException e)
      {

         success = false;
      } // end catch: IOException
      finally
      {
         try
         {
            bos.close();
         }
         catch (Exception e)
         {}
      } // end finally

      return success;
   } // end encodeToFile

   /**
    * Convenience method for decoding data to a file.
    *
    * @param dataToDecode Base64-encoded data as a string
    * @param filename Filename for saving decoded data
    * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
    *
    * @since 2.1
    */
   public static boolean decodeToFile(final String dataToDecode, final String filename)
   {
      boolean success = false;
      Base64.OutputStream bos = null;
      try
      {
         bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
         bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
         success = true;
      } // end try
      catch (java.io.IOException e)
      {
         success = false;
      } // end catch: IOException
      finally
      {
         try
         {
            bos.close();
         }
         catch (Exception e)
         {}
      } // end finally

      return success;
   } // end decodeToFile

   /**
    * Convenience method for reading a base64-encoded file and decoding it.
    *
    * @param filename Filename for reading encoded data
    * @return decoded byte array or null if unsuccessful
    *
    * @since 2.1
    */
   public static byte[] decodeFromFile(final String filename)
   {
      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 IllegalStateException("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;

         // 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 new IllegalStateException("Error decoding from file " + filename);
      } // end catch: 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.
    *
    * @param filename Filename for reading binary data
    * @return base64-encoded string or null if unsuccessful
    *
    * @since 2.1
    */
   public static String encodeFromFile(final String filename)
   {
      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[(int) (file.length() * 1.4)];
         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;

         // Save in a variable to return
         encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);

      } // end try
      catch (java.io.IOException e)
      {
         throw new IllegalStateException("Error encoding from file " + filename);
      } // end catch: IOException
      finally
      {
         try
         {
            bis.close();
         }
         catch (Exception e)
         {}
      } // end finally

      return encodedData;
   } // end encodeFromFile

   /* ********  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 <tt>java.io.InputStream</tt>, 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 final boolean encode; // Encoding or decoding
      private int position; // Current position in the buffer
      private final byte[] buffer; // Small buffer holding converted data
      private final int bufferLength; // Length of buffer (3 or 4)
      private int numSigBytes; // Number of meaningful bytes in the buffer
      private int lineLength;
      private final boolean breakLines; // Break lines at less than 80 characters

      /**
       * Constructs a {@link Base64.InputStream} in DECODE mode.
       *
       * @param in the <tt>java.io.InputStream</tt> from which to read data.
       * @since 1.3
       */
      public InputStream(final 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.
       *   DONT_BREAK_LINES: don't break lines at 76 characters
       *     (only meaningful when encoding)
       *     Note: Technically, this makes your encoding non-compliant.
       *

       *

       * Example: new Base64.InputStream( in, Base64.DECODE )
       *
       *
       * @param in the <tt>java.io.InputStream</tt> from which to read data.
       * @param options Specified options
       * @see Base64#ENCODE
       * @see Base64#DECODE
       * @see Base64#DONT_BREAK_LINES
       * @since 2.0
       */
      public InputStream(final java.io.InputStream in, final int options)
      {
         super(in);
         this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
         this.encode = (options & ENCODE) == ENCODE;
         this.bufferLength = encode ? 4 : 3;
         this.buffer = new byte[bufferLength];
         this.position = -1;
         this.lineLength = 0;
      } // 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++)
               {
                  try
                  {
                     int b = in.read();

                     // If end of stream, b is -1.
                     if (b >= 0)
                     {
                        b3[i] = (byte) b;
                        numBinaryBytes++;
                     } // end if: not end of stream

                  } // end try: read
                  catch (java.io.IOException e)
                  {
                     // Only a problem if we got no data at all.
                     if (i == 0)
                        throw e;

                  } // end catch
               } // end for: each needed input byte

               if (numBinaryBytes > 0)
               {
                  encode3to4(b3, 0, numBinaryBytes, buffer, 0);
                  position = 0;
                  numSigBytes = 4;
               } // end if: got data
               else
               {
                  return -1;
               } // 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

                  b4[i] = (byte) b;
               } // end for: each needed input byte

               if (i == 4)
               {
                  numSigBytes = decode4to3(b4, 0, buffer, 0);
                  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;

            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;

               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
         {
            // When JDK1.4 is more accepted, use an assertion here.
            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 <var>len</var> 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(final byte[] dest, final int off, final int len) throws java.io.IOException
      {
         int i;
         int b;
         for (i = 0; i < len; i++)
         {
            b = read();

            // if( b < 0 && i == 0 )
            // return -1;

            if (b >= 0)
               dest[off + i] = (byte) b;
            else if (i == 0)
               return -1;
            else
               break; // 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 <tt>java.io.OutputStream</tt>, 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 final boolean encode;
      private int position;
      private byte[] buffer;
      private final int bufferLength;
      private int lineLength;
      private final boolean breakLines;
      private final byte[] b4; // Scratch used in a few places
      private boolean suspendEncoding;

      /**
       * Constructs a {@link Base64.OutputStream} in ENCODE mode.
       *
       * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
       * @since 1.3
       */
      public OutputStream(final 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.
       *   DONT_BREAK_LINES: don't break lines at 76 characters
       *     (only meaningful when encoding)
       *     Note: Technically, this makes your encoding non-compliant.
       *

       *

       * Example: new Base64.OutputStream( out, Base64.ENCODE )
       *
       * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
       * @param options Specified options.
       * @see Base64#ENCODE
       * @see Base64#DECODE
       * @see Base64#DONT_BREAK_LINES
       * @since 1.3
       */
      public OutputStream(final java.io.OutputStream out, final int options)
      {
         super(out);
         this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
         this.encode = (options & ENCODE) == ENCODE;
         this.bufferLength = encode ? 3 : 4;
         this.buffer = new byte[bufferLength];
         this.position = 0;
         this.lineLength = 0;
         this.suspendEncoding = false;
         this.b4 = new byte[4];
      } // 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(final int theByte) throws java.io.IOException
      {
         // Encoding suspended?
         if (suspendEncoding)
         {
            super.out.write(theByte);
            return;
         } // end if: supsended

         // Encode?
         if (encode)
         {
            buffer[position++] = (byte) theByte;
            if (position >= bufferLength) // Enough to encode.
            {
               out.write(encode3to4(b4, buffer, bufferLength));

               lineLength += 4;
               if (breakLines && (lineLength >= MAX_LINE_LENGTH))
               {
                  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);
                  out.write(b4, 0, len);
                  // out.write( Base64.decode4to3( buffer ) );
                  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 <var>len</var> 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(final byte[] theBytes, final int off, final int len) throws java.io.IOException
      {
         // Encoding suspended?
         if (suspendEncoding)
         {
            super.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 IOException
       */
      public void flushBase64() throws java.io.IOException
      {
         if (position > 0)
         {
            if (encode)
            {
               out.write(encode3to4(b4, buffer, position));
               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 base640-encoded data in a
       * stream.
       *
       * @since 1.5.1
       * @throws IOException
       */
      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 base640-encoded data in a
       * stream.
       *
       * @since 1.5.1
       */
      public void resumeEncoding()
      {
         this.suspendEncoding = false;
      } // end resumeEncoding

   } // end inner class OutputStream

} // end class Base64


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.config;

import java.util.Collections;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.common.pattern.WeightedComparator;
import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.common.util.Iterators;
import org.ocpsoft.common.util.Strings;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.servlet.DispatcherType;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.DispatcherTypeProvider;

/**
 * A {@link Condition} that inspects the value of {@link HttpServletRequest#getDispatcherType()}.
 *
 * @author Lincoln Baxter, III
 */
public class DispatchType extends HttpCondition
{
   private final static String PROVIDER_KEY = DispatchType.class.getName() + "_PROVIDERS";

   private final DispatcherType type;

   private DispatchType(final DispatcherType type)
   {
      this.type = type;
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      if (this.type.equals(getDispatcherType(event)))
      {
         return true;
      }
      return false;
   }

   /**
    * Determines the {@link DispatcherType} of the current request using the {@link DispatcherTypeProvider} SPI.
    */
   private DispatcherType getDispatcherType(final HttpServletRewrite event)
   {
      for (DispatcherTypeProvider provider : getDispatcherTypeProviders(event)) {
         DispatcherType dispatcherType = provider.getDispatcherType(event.getRequest(), event.getServletContext());
         if (dispatcherType != null) {
            return dispatcherType;
         }
      }
      throw new IllegalStateException("Unable to determine dispatcher type of current request");
   }

   /**
    * Simple caching mechanism for the providers on a per request basis
    */
   @SuppressWarnings("unchecked")
   private List<DispatcherTypeProvider> getDispatcherTypeProviders(HttpServletRewrite event)
   {
      List<DispatcherTypeProvider> providers = (List<DispatcherTypeProvider>)
               event.getRequest().getAttribute(PROVIDER_KEY);
      if (providers == null) {
         providers = Iterators.asList(ServiceLoader.loadTypesafe(DispatcherTypeProvider.class).iterator());
         Collections.sort(providers, new WeightedComparator());
         event.getRequest().setAttribute(PROVIDER_KEY, providers);
      }
      return providers;
   }

   /**
    * Create a {@link DispatchType} condition that ensures the current {@link HttpServletRewrite} is a
    * {@link DispatcherType#FORWARD}
    */
   public static DispatchType isForward()
   {
      return new DispatchType(DispatcherType.FORWARD);
   }

   /**
    * Create a {@link DispatchType} condition that ensures the current {@link HttpServletRewrite} is a
    * {@link DispatcherType#REQUEST}
    */
   public static DispatchType isRequest()
   {
      return new DispatchType(DispatcherType.REQUEST);
   }

   /**
    * Create a {@link DispatchType} condition that ensures the current {@link HttpServletRewrite} is a
    * {@link DispatcherType#ERROR}
    */
   public static DispatchType isError()
   {
      return new DispatchType(DispatcherType.ERROR);
   }

   /**
    * Create a {@link DispatchType} condition that ensures the current {@link HttpServletRewrite} is a
    * {@link DispatcherType#ASYNC}
    */
   public static DispatchType isAsync()
   {
      return new DispatchType(DispatcherType.ASYNC);
   }

   /**
    * Create a {@link DispatchType} condition that ensures the current {@link HttpServletRewrite} is a
    * {@link DispatcherType#INCLUDE}
    */
   public static DispatchType isInclude()
   {
      return new DispatchType(DispatcherType.INCLUDE);
   }

   @Override
   public String toString()
   {
      return "DispatchType.is" + Strings.capitalize(type.toString().toLowerCase() + "()");
   }

}


package org.ocpsoft.rewrite.servlet.util;

import javax.servlet.ServletContext;

import org.ocpsoft.common.util.Assert;

/**
 * Utility method for easily interacting with the {@link ServletContext}
 *
 * @author Lincoln Baxter, III
 *
 */
public class ContextUtil
{
   /**
    * Retrieve the named context-parameter from the given {@link ServletContext}. If the parameter is not defined,
    * return false, otherwise return the value as a boolean. If the value cannot be converted to a boolean type, return
    * false.
    */
   public static boolean getInitParamBoolean(ServletContext context, String name)
   {
      boolean result = false;
      String value = getInitParam(context, name, null);
      if ("true".equalsIgnoreCase(value))
      {
         result = true;
      }
      return result;
   }

   /**
    * Retrieve the named context-parameter from the given {@link ServletContext}. If the parameter is not defined,
    * return null.
    */
   public static String getInitParam(ServletContext context, String name)
   {
      return getInitParam(context, name, null);
   }

   /**
    * Retrieve the named context-parameter from the given {@link ServletContext}. If the parameter is not defined,
    * return the default value instead.
    */
   public static String getInitParam(ServletContext context, String name, String deflt)
   {
      Assert.notNull(context, "Servlet context must not be null.");
      Assert.notNull(context, "Cookie name must not be null.");

      String value = context.getInitParameter(name);

      if (value == null)
      {
         value = deflt;
      }

      return value;
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.bind.Retrieval;
import org.ocpsoft.rewrite.bind.Submission;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.spi.InvocationResultHandler;

/**
 * Builds {@link Operation} instances used to directly invoke {@link Binding} submission or retrieval on {@link Rewrite}
 * events.
 *
 * @author Lincoln Baxter, III
 */
public abstract class Invoke extends DefaultOperationBuilder
{
   private static final Logger log = Logger.getLogger(Invoke.class);
   private final Submission submission;
   private final Retrieval retrieval;

   private Invoke(final Submission submission, final Retrieval retrieval)
   {
      this.submission = submission;
      this.retrieval = retrieval;
   }

   @Override
   @SuppressWarnings("unchecked")
   public void perform(final Rewrite event, final EvaluationContext context)
   {
      Object result = null;
      if ((submission == null) && (retrieval != null))
      {
         result = retrieval.retrieve(event, context);
         log.debug("Invoked binding [" + retrieval + "] returned value [" + result + "]");
      }
      else if (retrieval != null)
      {
         // TODO convert/validate here?
         result = submission.submit(event, context, retrieval.retrieve(event, context));
         log.debug("Invoked binding [" + submission + "] returned value [" + result + "]");
      }
      else
      {
         log.warn("No binding specified for Invocation.");
      }

      if (result != null)
      {
         ServiceLoader<InvocationResultHandler> providers = ServiceLoader.load(InvocationResultHandler.class);
         if (!providers.iterator().hasNext())
         {
            log.debug("No instances of [" + InvocationResultHandler.class.getName()
                     + "] were registered to handing binding invocation result [" + result + "]");
         }

         for (InvocationResultHandler handler : providers) {
            handler.handle(event, context, result);
         }
      }
   }

   /**
    * Invoke the given {@link Retrieval} and process {@link InvocationResultHandler} instances on the result value (if
    * any.)
    */
   public static DefaultOperationBuilder binding(final Retrieval retrieval)
   {
      return new Invoke(null, retrieval){

         @Override
         public String toString()
         {
            return "Invoke.binding(" + retrieval + ")";
         }

      };
   }

   /**
    *
    * Invoke {@link Submission#submit(Rewrite, EvaluationContext, Object)}, use the result of the given
    * {@link Retrieval#retrieve(Rewrite, EvaluationContext)} as the value for this submission. Process
    * {@link InvocationResultHandler} instances on the result value (if any.)
    */
   public static DefaultOperationBuilder binding(final Submission to, final Retrieval from)
   {
      return new Invoke(to, from) {

         @Override
         public String toString()
         {
            return "Invoke.binding(" + to + ", " + from + ")";
         }

      };
   }

   @Override
   public abstract String toString();
}


/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.ocpsoft.rewrite;

import java.util.ArrayList;
import java.util.List;

import org.ocpsoft.logging.Logger;
import org.ocpsoft.logging.Logger.Level;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.config.RuleMetadata;
import org.ocpsoft.rewrite.context.Context;
import org.ocpsoft.rewrite.context.ContextBase;
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * @author Lincoln Baxter, III
 *
 */
public abstract class AbstractRewrite implements Rewrite
{
   private static final Logger log = Logger.getLogger(AbstractRewrite.class);

   private Context context = new ContextBase() {};

   @Override
   public Context getRewriteContext()
   {
      return context;
   }

   @Override
   @SuppressWarnings("unchecked")
   public List<Rule> getEvaluatedRules()
   {
      List<Rule> evaluated = (List<Rule>) this.getRewriteContext().get(
               AbstractRewrite.class.getName() + "_EVALUATED_RULES");
      if (evaluated == null)
      {
         evaluated = new ArrayList<Rule>();
         this.getRewriteContext().put(AbstractRewrite.class.getName() + "_EVALUATED_RULES", evaluated);
      }
      return evaluated;
   }

   /**
    * Print a log of all {@link Rule} instances evaluated during the given {@link Rewrite} event. This method is a no-op
    * if the selected logging {@link Level} is disabled.
    */
   public static void logEvaluatedRules(Rewrite event, Level level)
   {
      switch (level)
      {
      case INFO:
         if (log.isInfoEnabled())
            log.info(buildLogOutput(event).toString());
         break;
      case WARN:
         if (log.isWarnEnabled())
            log.warn(buildLogOutput(event).toString());
         break;
      case ERROR:
         if (log.isErrorEnabled())
            log.error(buildLogOutput(event).toString());
         break;
      case DEBUG:
         if (log.isDebugEnabled())
            log.debug(buildLogOutput(event).toString());
         break;
      case TRACE:
         if (log.isTraceEnabled())
            log.trace(buildLogOutput(event).toString());
         break;

      default:
         break;
      }
   }

   private static StringBuilder buildLogOutput(Rewrite event)
   {
      StringBuilder builder = new StringBuilder();
      builder.append("Rewrite rule evaluation for event [" + event + "]\n");

      int i = 0;
      for (Rule rule : event.getEvaluatedRules()) {
         builder.append("\tRule " + i + ": " + rule);
         if (rule instanceof Context)
         {
            builder.append(" defined at "
                     + ((Context) rule).get(RuleMetadata.PROVIDER_LOCATION) + "\n");
         }
         i++;
      }
      return builder;
   }
}


package org.ocpsoft.rewrite.param;

/**
 * Thrown when a syntax error is found in a {@link ParameterizedPattern}.
 *
 * @author Lincoln Baxter, III
 */
public class ParameterizedPatternSyntaxException extends IllegalArgumentException
{
   private static final long serialVersionUID = -7847918960698384107L;
   private final String message;
   private final String pattern;
   private final int index;

   /**
    * Create a new {@link ParameterizedPatternSyntaxException}.
    *
    * @param message The error message
    * @param pattern The pattern that contains errors
    * @param index The location of the error, or <tt>-1</tt> if location is unknown
    */
   public ParameterizedPatternSyntaxException(String message, String pattern, int index)
   {
      super(message);
      this.message = message;
      this.pattern = pattern;
      this.index = index;
   }

   /**
    * Returns the index at which the error is located in the pattern, or <tt>-1</tt> if the location is not known.
    */
   public int getIndex()
   {
      return index;
   }

   /**
    * Returns the pattern containing errors.
    */
   public String getPattern()
   {
      return pattern;
   }

   /**
    * Returns a string containing the description of the syntax error and its location, the erroneous parameterized
    * pattern string, and a visual location of the error index within the pattern (if possible.)
    */
   @Override
   public String getMessage()
   {
      String nl = System.getProperty("line.separator");
      StringBuffer sb = new StringBuffer();
      sb.append(message);
      if (index >= 0) {
         sb.append(" near index ");
         sb.append(index);
      }
      sb.append(nl);
      sb.append(pattern);
      if (index >= 0) {
         sb.append(nl);
         for (int i = 0; i < index; i++)
            sb.append(' ');
         sb.append('^');
      }
      return sb.toString();
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.ocpsoft.common.pattern.WeightedComparator;
import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.common.util.Iterators;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.bind.Evaluation;
import org.ocpsoft.rewrite.context.Context;
import org.ocpsoft.rewrite.param.ConfigurableParameter;
import org.ocpsoft.rewrite.param.DefaultParameter;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedRule;
import org.ocpsoft.rewrite.spi.ConfigurationCacheProvider;
import org.ocpsoft.rewrite.util.Visitor;

/**
 * Responsible for loading all {@link ConfigurationProvider} instances, and building a single unified
 * {@link Configuration} based on {@link ConfigurationProvider#priority()}
 *
 * @author Lincoln Baxter, III
 */
public class ConfigurationLoader
{
   public static Logger log = Logger.getLogger(ConfigurationLoader.class);
   private final List<ConfigurationCacheProvider<?>> caches;
   private final List<ConfigurationProvider<?>> providers;

   @SuppressWarnings({ "unchecked" })
   public ConfigurationLoader(Object context)
   {
      caches = Iterators.asList(ServiceLoader.load(ConfigurationCacheProvider.class));
      Collections.sort(caches, new WeightedComparator());

      providers = Iterators.asList(ServiceLoader.load(ConfigurationProvider.class));
      Collections.sort(providers, new WeightedComparator());
   }

   /**
    * Get a new {@link ConfigurationLoader} instance.
    */
   public static ConfigurationLoader create(final Object context)
   {
      return new ConfigurationLoader(context);
   }

   /**
    * Load all {@link ConfigurationProvider} instances, sort by {@link ConfigurationProvider#priority()}, and return a
    * unified, composite {@link Configuration} object.
    */
   public Configuration loadConfiguration(Object context)
   {
      if (caches.isEmpty())
      {
         return build(context);
      }
      return buildCached(context);
   }

   @SuppressWarnings({ "rawtypes", "unchecked" })
   private Configuration buildCached(Object context)
   {
      Configuration result = null;
      /*
       * Do not force synchronization if a configuration is primed.
       */
      for (ConfigurationCacheProvider cache : caches) {
         Configuration cachedConfig = cache.getConfiguration(context);
         if (cachedConfig != null)
         {
            result = cachedConfig;
            break;
         }
      }

      if (result == null)
      {
         synchronized (this) {

            /*
             * Double check in order to ensure that a configuration wasn't built after our first cache check.
             */
            for (ConfigurationCacheProvider cache : caches) {
               Configuration cachedConfig = cache.getConfiguration(context);
               if (cachedConfig != null)
               {
                  result = cachedConfig;
                  break;
               }
            }

            if (result == null)
            {
               result = build(context);

               for (ConfigurationCacheProvider cache : caches) {
                  cache.setConfiguration(context, result);
               }
            }
         }
      }

      return result;
   }

   @SuppressWarnings({ "rawtypes", "unchecked" })
   private Configuration build(Object context)
   {

      Map<Integer, List<Rule>> priorityMap = new LinkedHashMap<Integer, List<Rule>>();
      for (ConfigurationProvider provider : providers) {
         if (provider.handles(context))
         {
            Configuration configuration = provider.getConfiguration(context);

            if (configuration != null)
            {
               List<Rule> rules = configuration.getRules();
               if (rules != null)
               {
                  for (Rule rule : rules) {
                     if (rule != null)
                     {
                        if (rule instanceof RelocatableRule && ((RelocatableRule) rule).isRelocated())
                           addListValue(priorityMap, ((RelocatableRule) rule).priority(), rule);
                        else
                           addListValue(priorityMap, provider.priority(), rule);
                     }
                     else {
                        log.debug("Ignoring null Rule from ConfigurationProvider ["
                                 + provider.getClass().getName()
                                 + "]");
                     }
                  }
               }
               else {
                  log.debug("Ignoring null List<Rule> from ConfigurationProvider ["
                           + provider.getClass().getName()
                           + "]");
               }
            }
            else {
               log.debug("Ignoring null Configuration from ConfigurationProvider ["
                        + provider.getClass().getName()
                        + "].");
            }
         }
      }

      ConfigurationBuilder result = ConfigurationBuilder.begin();
      ArrayList<Integer> sortedKeys = new ArrayList<Integer>(priorityMap.keySet());
      Collections.sort(sortedKeys);

      for (Integer integer : sortedKeys) {
         List<Rule> list = priorityMap.get(integer);
         for (final Rule rule : list) {
            result.addRule(rule);

            try {
               if (rule instanceof ParameterizedRule) {
                  ParameterizedCallback callback = new ParameterizedCallback() {
                     @Override
                     public void call(Parameterized parameterized)
                     {
                        Set<String> names = parameterized.getRequiredParameterNames();
                        ParameterStore store = ((ParameterizedRule) rule).getParameterStore();

                        if (names != null)
                           for (String name : names) {
                              Parameter<?> parameter = store.get(name, new DefaultParameter(name));
                              if (parameter instanceof ConfigurableParameter<?>)
                                 ((ConfigurableParameter<?>) parameter).bindsTo(Evaluation.property(name));
                           }

                        parameterized.setParameterStore(store);
                     }
                  };

                  Visitor<Condition> conditionVisitor = new ParameterizedConditionVisitor(callback);
                  new ConditionVisit(rule).accept(conditionVisitor);

                  Visitor<Operation> operationVisitor = new ParameterizedOperationVisitor(callback);
                  new OperationVisit(rule).accept(operationVisitor);
               }
            }
            catch (RuntimeException e) {
               String message = "Error encountered while visiting rule: " + rule;

               if (rule instanceof Context)
               {
                  message += " defined at " + ((Context) rule).get(RuleMetadata.PROVIDER_LOCATION) + "\n";
               }
               log.error(message);
               throw e;
            }
         }
      }

      return result;
   }

   @SuppressWarnings("unchecked")
   public static <K, T> void addListValue(final Map<K, List<T>> map, final K key, final T value)
   {
      if (!map.containsKey(key))
      {
         map.put(key, new ArrayList<T>(Arrays.asList(value)));
      }
      else
      {
         map.get(key).add(value);
      }
   }

}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.config;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;

/**
 * A {@link Condition} that inspects values returned by {@link HttpServletRequest#getParameterMap()}
 *
 * @author Lincoln Baxter, III
 */
public abstract class RequestParameter extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser name;
   private final ParameterizedPatternParser value;

   private RequestParameter(final String name, final String value)
   {
      Assert.notNull(name, "Header name pattern cannot be null.");
      Assert.notNull(value, "Header value pattern cannot be null.");
      this.name = new RegexParameterizedPatternParser(name);
      this.value = new RegexParameterizedPatternParser(value);
   }

   /**
    * Create a {@link Condition} that matches against both request parameter names and values.
    *

    * Parameter name and value expressions may be parameterized:
    *

    *
    * RequestParameter.matches("username", "guest")

    * RequestParameter.matches("username", "{name}")

    * RequestParameter.matches("{anything}", "{value}")

    *
    *
    * @param name {@link ParameterizedPattern} matching the request parameter name.
    * @param value {@link ParameterizedPattern} matching the request parameter value.
    *
    * @see ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getParameterMap()}
    */
   public static RequestParameter matches(final String name, final String value)
   {
      return new RequestParameter(name, value) {
         @Override
         public String toString()
         {
            return "RequestParameter.matches(\"" + name + "\", \"" + value + "\")";
         }
      };
   }

   public static RequestParameter matchesAll(final String name, final String value)
   {
      return new AllRequestParameters(name, value) {
         @Override
         public String toString()
         {
            return "RequestParameter.matchesAll(\"" + name + "\", \"" + value + "\")";
         }
      };
   }

   /**
    * Creates a {@link RequestParameter} condition that will capture the value of the the given request parameter if it
    * exists so you can bind to it using .where().
    *
    * @param name The name of the request parameter
    */
   public static RequestParameter captureValue(final String name)
   {
      return new RequestParameter(name, "{" + name + "}") {

         @Override
         public String toString()
         {
            return "RequestParameter.captureValue(\"" + name + "\")";
         }

         @Override
         public boolean evaluateHttp(HttpServletRewrite event, EvaluationContext context)
         {
            super.evaluateHttp(event, context);
            return true;
         }

      };
   }

   /**
    * Create a {@link Condition} that matches against the existence of a request parameter with a name matching the
    * given pattern. The parameter value is ignored.
    *

    * Parameter name expressions may be parameterized:
    *

    *
    * RequestParameter.exists("username")

    * RequestParameter.exists("{name}")

    * ...
    *
    *
    * @param name {@link ParameterizedPattern} matching the request parameter name.
    *
    * @see ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getParameterMap()}
    */
   public static RequestParameter exists(final String name)
   {
      return new RequestParameter(name, "{" + RequestParameter.class.getName() + "_" + name + "_value}") {
         @Override
         public String toString()
         {
            return "RequestParameter.exists(\"" + name + "\")";
         }
      };
   }

   /**
    * Create a {@link Condition} that matches only against the existence of a request parameter value matching the given
    * pattern. The parameter name is ignored.
    *

    * Parameter value expressions may be parameterized:
    *

    *
    * RequestParameter.valueExists("guest")

    * RequestParameter.valueExists("{username}")

    * ...
    *
    *
    * @param name {@link ParameterizedPattern} matching the request parameter name.
    *
    * @see ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getParameterMap()}
    */
   public static RequestParameter valueExists(final String value)
   {
      return new RequestParameter("{" + RequestParameter.class.getName() + "_" + value + "_name}", value) {
         @Override
         public String toString()
         {
            return "RequestParameter.valueExists(\"" + value + "\")";
         }
      };
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      HttpServletRequest request = event.getRequest();
      Enumeration<?> parameterNames = request.getParameterNames();
      while (parameterNames.hasMoreElements())
      {
         String parameter = parameterNames.nextElement().toString();
         if (name.parse(parameter).submit(event, context) && matchesValue(event, context, request, parameter))
         {
            return true;
         }
      }
      return false;
   }

   private boolean matchesValue(Rewrite event, EvaluationContext context, final HttpServletRequest request,
            final String parameterName)
   {
      for (String contents : Arrays.asList(request.getParameterValues(parameterName)))
      {
         if (value.parse(contents).submit(event, context))
         {
            return true;
         }
      }
      return false;
   }

   /**
    * Get the {@link ParameterizedPattern} of the request parameter name.
    */
   public ParameterizedPatternParser getNameExpression()
   {
      return name;
   }

   /**
    * Get the {@link ParameterizedPattern} of the request parameter value.
    */
   public ParameterizedPatternParser getValueExpression()
   {
      return value;
   }

   private abstract static class AllRequestParameters extends RequestParameter
   {
      public AllRequestParameters(String name, String value)
      {
         super(name, value);
      }

      @Override
      public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
      {
         HttpServletRequest request = event.getRequest();
         Enumeration<?> parameterNames = request.getParameterNames();
         while (parameterNames.hasMoreElements())
         {
            String name = parameterNames.nextElement().toString();
            if (getNameExpression().parse(name).submit(event, context))
            {
               if (matchesValues(event, context, request, name))
               {
                  return true;
               }
            }
         }
         return false;
      }

      private boolean matchesValues(Rewrite event, EvaluationContext context, final HttpServletRequest request,
               final String name)
      {
         for (String contents : Arrays.asList(request.getParameterValues(name)))
         {
            if (!getValueExpression().parse(contents).submit(event, context))
            {
               return false;
            }
         }
         return true;
      }
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      Set<String> result = new LinkedHashSet<String>();
      result.addAll(name.getRequiredParameterNames());
      result.addAll(value.getRequiredParameterNames());
      return result;
   }

   @Override
   public void setParameterStore(ParameterStore store)
   {
      name.setParameterStore(store);
      value.setParameterStore(store);
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

import java.util.Arrays;
import java.util.List;

import org.ocpsoft.rewrite.config.DefaultConditionBuilder.DefaultConditionBuilderInternal;
import org.ocpsoft.rewrite.context.EvaluationContext;
/*
 * Copyright 2013 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * Utility for creating and wrapping {@link Condition} instances.
 *
 * @author Lincoln Baxter, III
 */
public final class Conditions
{
   public static final String NEGATION_COUNT_KEY = Conditions.class.getCanonicalName() + "_NotCount";

   /**
    * Indicates whether the current {@link Condition} is being negated.
    */
   public static boolean isNegated(final EvaluationContext context)
   {
      return getNegationCount(context) % 2 == 1;
   }

   /**
    * Returns the number of "Not"s that have been evaluated in the current evaluation process.
    *
    * For example, when(Not.any(MyCondition)) would have a "NotCount" of "1" during the evaluation of "MyCondition".
    *
    * when(Not.any(Not.any(MyCondition))) would have a "NotCount" of "2" during the evaluation of "MyCondition".
    *
    * This is useful for conditions that may have side effects, as they will know whether or not their condition is being
    * negated.
    */
   public static int getNegationCount(final EvaluationContext context)
   {
      if (context == null)
         return 0;

      Integer count = (Integer)context.get(NEGATION_COUNT_KEY);
      return count == null ? 0 : count;
   }

   /**
    * This increments the number of negations, allowing us to determine (during evaluation) whether the results of a particular
    * {@link Condition} will be negated.
    */
   static void incrementNegationCount(final EvaluationContext context, int adjustment)
   {
      if (context == null)
         return;

      Integer count = Conditions.getNegationCount(context);
      count += adjustment;
      context.put(NEGATION_COUNT_KEY, count);
   }

   /**
    * Return a new {@link DefaultConditionBuilder} that evaluates to {@link True} when
    * {@link #evaluate(Rewrite, EvaluationContext)} is invoked.
    */
   public static ConditionBuilder create()
   {
      return new DefaultConditionBuilder() {
         @Override
         public boolean evaluate(Rewrite event, EvaluationContext context)
         {
            return true;
         }

         @Override
         public String toString()
         {
            return "";
         }
      };
   }

   /**
    * Wrap a given {@link Condition} as a new {@link DefaultConditionBuilder} that evaluates the the original
    * {@link Condition} when {@link #evaluate(Rewrite, EvaluationContext)} is invoked.
    */
   public static ConditionBuilder wrap(final Condition condition)
   {
      if (condition == null)
         return create();

      if (condition instanceof ConditionBuilder)
         return (ConditionBuilder) condition;

      return new DefaultConditionBuilderInternal(condition) {
         @Override
         public boolean evaluate(Rewrite event, EvaluationContext context)
         {
            return condition.evaluate(event, context);
         }

         @Override
         public String toString()
         {
            return condition.toString();
         }

         @Override
         public List<Condition> getConditions()
         {
            return Arrays.asList(condition);
         }
      };
   }
}