Package org.apache.click.util

Source Code of org.apache.click.util.ClickUtils

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.apache.click.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.click.Context;
import org.apache.click.Control;
import org.apache.click.Page;
import org.apache.click.control.Form;
import org.apache.click.service.ConfigService;
import org.apache.click.service.LogService;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;

/**
* Provides miscellaneous Form, String and Stream utility methods.
*/
public class ClickUtils {

    // ------------------------------------------------------- Public Constants

    /**
     * The resource <tt>versioning</tt> request attribute: key: &nbsp;
     * <tt>enable-resource-version</tt>.
     * <p/>
     * If this attribute is set to <tt>true</tt> and Click is running in
     * <tt>production</tt> or <tt>profile</tt> mode, resources returned from
     * {@link org.apache.click.Control#getHtmlImports()} will have a
     * <tt>version indicator</tt> added to their path.
     *
     * @see org.apache.click.Control#getHtmlImports()
     * @see org.apache.click.util.ClickUtils#createHtmlImport(String, Context)
     * @see org.apache.click.util.ClickUtils#getResourceVersionIndicator(Context)
     */
    public static final String ENABLE_RESOURCE_VERSION = "enable-resource-version";

    /**
     * The default Click configuration filename: &nbsp;
     * "<tt>/WEB-INF/click.xml</tt>".
     */
    public static final String DEFAULT_APP_CONFIG = "/WEB-INF/click.xml";

    /** The version indicator separator string. */
    public static final String VERSION_INDICATOR_SEP = "_";

    /** The static web resource version number indicator string. */
    public static final String RESOURCE_VERSION_INDICATOR =
        VERSION_INDICATOR_SEP + getClickVersion();

    // ------------------------------------------------------ Private Constants

    /** The cached resource version indicator. */
    private static String cachedResourceVersionIndicator;

    /** The static application-wide resource version indicator. */
    private static String applicationVersion;

    /** The cached application version indicator string. */
    private static String cachedApplicationVersionIndicator;

    /**
     * Character used to separate username and password in persistent cookies.
     * 0x13 == "Device Control 3" non-printing ASCII char. Unlikely to appear
     * in a username
     */
    private static final char DELIMITER = 0x13;

    /*
     * "Tweakable" parameters for the cookie encoding. NOTE: changing these
     * and recompiling this class will essentially invalidate old cookies.
     */
    private final static char ENCODE_CHAR_OFFSET1 = 'C';
    private final static char ENCODE_CHAR_OFFSET2 = 'i';

    /** Hexadecimal characters for MD5 encoding. */
    private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5',
        '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * The array of escaped HTML character values, indexed on char value.
     * <p/>
     * HTML entities values were derived from Jakarta Commons Lang
     * <tt>org.apache.commons.lang.Entities</tt> class.
     */
    private static final String[] HTML_ENTITIES = new String[9999];

    static {
        HTML_ENTITIES[34] = "&quot;"; // " - double-quote
        HTML_ENTITIES[38] = "&amp;"; // & - ampersand
        HTML_ENTITIES[60] = "&lt;"; // < - less-than
        HTML_ENTITIES[62] = "&gt;"; // > - greater-than
        HTML_ENTITIES[160] = "&nbsp;"; // non-breaking space
        HTML_ENTITIES[161] = "&iexcl;"; // inverted exclamation mark
        HTML_ENTITIES[162] = "&cent;"// cent sign
        HTML_ENTITIES[163] = "&pound;"; // pound sign
        HTML_ENTITIES[164] = "&curren;"; // currency sign
        HTML_ENTITIES[165] = "&yen;"// yen sign = yuan sign
        HTML_ENTITIES[166] = "&brvbar;"; // broken bar = broken vertical bar
        HTML_ENTITIES[167] = "&sect;"; // section sign
        HTML_ENTITIES[168] = "&uml;"; // diaeresis = spacing diaeresis
        HTML_ENTITIES[169] = "&copy;"; // © - copyright sign
        HTML_ENTITIES[170] = "&ordf;"; // feminine ordinal indicator
        HTML_ENTITIES[171] = "&laquo;"; // left-pointing double angle quotation mark = left pointing guillemet
        HTML_ENTITIES[172] = "&not;";   //not sign
        HTML_ENTITIES[173] = "&shy;";   //soft hyphen = discretionary hyphen
        HTML_ENTITIES[174] = "&reg;";   // ® - registered trademark sign
        HTML_ENTITIES[175] = "&macr;";   //macron = spacing macron = overline = APL overbar
        HTML_ENTITIES[176] = "&deg;";   //degree sign
        HTML_ENTITIES[177] = "&plusmn;";   //plus-minus sign = plus-or-minus sign
        HTML_ENTITIES[178] = "&sup2;";   //superscript two = superscript digit two = squared
        HTML_ENTITIES[179] = "&sup3;";   //superscript three = superscript digit three = cubed
        HTML_ENTITIES[180] = "&acute;";   //acute accent = spacing acute
        HTML_ENTITIES[181] = "&micro;";   //micro sign
        HTML_ENTITIES[182] = "&para;";   //pilcrow sign = paragraph sign
        HTML_ENTITIES[183] = "&middot;";   //middle dot = Georgian comma = Greek middle dot
        HTML_ENTITIES[184] = "&cedil;";   //cedilla = spacing cedilla
        HTML_ENTITIES[185] = "&sup1;";   //superscript one = superscript digit one
        HTML_ENTITIES[186] = "&ordm;";   //masculine ordinal indicator
        HTML_ENTITIES[187] = "&raquo;";   //right-pointing double angle quotation mark = right pointing guillemet
        HTML_ENTITIES[188] = "&frac14;";   //vulgar fraction one quarter = fraction one quarter
        HTML_ENTITIES[189] = "&frac12;";   //vulgar fraction one half = fraction one half
        HTML_ENTITIES[190] = "&frac34;";   //vulgar fraction three quarters = fraction three quarters
        HTML_ENTITIES[191] = "&iquest;";   //inverted question mark = turned question mark
        HTML_ENTITIES[192] = "&Agrave;";   // À - uppercase A, grave accent
        HTML_ENTITIES[193] = "&Aacute;";   // Á - uppercase A, acute accent
        HTML_ENTITIES[194] = "&Acirc;";   // Â - uppercase A, circumflex accent
        HTML_ENTITIES[195] = "&Atilde;";   // Ã - uppercase A, tilde
        HTML_ENTITIES[196] = "&Auml;";   // Ä - uppercase A, umlaut
        HTML_ENTITIES[197] = "&Aring;";   // Å - uppercase A, ring
        HTML_ENTITIES[198] = "&AElig;";   // Æ - uppercase AE
        HTML_ENTITIES[199] = "&Ccedil;";   // Ç - uppercase C, cedilla
        HTML_ENTITIES[200] = "&Egrave;";   // È - uppercase E, grave accent
        HTML_ENTITIES[201] = "&Eacute;";   // É - uppercase E, acute accent
        HTML_ENTITIES[202] = "&Ecirc;";   // Ê - uppercase E, circumflex accent
        HTML_ENTITIES[203] = "&Euml;";   // Ë - uppercase E, umlaut
        HTML_ENTITIES[204] = "&Igrave;";   // Ì - uppercase I, grave accent
        HTML_ENTITIES[205] = "&Iacute;";   // Í - uppercase I, acute accent
        HTML_ENTITIES[206] = "&Icirc;";   // Î - uppercase I, circumflex accent
        HTML_ENTITIES[207] = "&Iuml;";   // Ï - uppercase I, umlaut
        HTML_ENTITIES[208] = "&ETH;";   // Ð - uppercase Eth, Icelandic
        HTML_ENTITIES[209] = "&Ntilde;";   // Ñ - uppercase N, tilde
        HTML_ENTITIES[210] = "&Ograve;";   // Ò - uppercase O, grave accent
        HTML_ENTITIES[211] = "&Oacute;";   // Ó - uppercase O, acute accent
        HTML_ENTITIES[212] = "&Ocirc;";   // Ô - uppercase O, circumflex accent
        HTML_ENTITIES[213] = "&Otilde;";   // Õ - uppercase O, tilde
        HTML_ENTITIES[214] = "&Ouml;";   // Ö - uppercase O, umlaut
        HTML_ENTITIES[215] = "&times;";   //multiplication sign
        HTML_ENTITIES[216] = "&Oslash;";   // Ø - uppercase O, slash
        HTML_ENTITIES[217] = "&Ugrave;";   // Ù - uppercase U, grave accent
        HTML_ENTITIES[218] = "&Uacute;";   // Ú - uppercase U, acute accent
        HTML_ENTITIES[219] = "&Ucirc;";   // Û - uppercase U, circumflex accent
        HTML_ENTITIES[220] = "&Uuml;";   // Ü - uppercase U, umlaut
        HTML_ENTITIES[221] = "&Yacute;";   // Ý - uppercase Y, acute accent
        HTML_ENTITIES[222] = "&THORN;";   // Þ - uppercase THORN, Icelandic
        HTML_ENTITIES[223] = "&szlig;";   // ß - lowercase sharps, German
        HTML_ENTITIES[224] = "&agrave;";   // à - lowercase a, grave accent
        HTML_ENTITIES[225] = "&aacute;";   // á - lowercase a, acute accent
        HTML_ENTITIES[226] = "&acirc;";   // â - lowercase a, circumflex accent
        HTML_ENTITIES[227] = "&atilde;";   // ã - lowercase a, tilde
        HTML_ENTITIES[228] = "&auml;";   // ä - lowercase a, umlaut
        HTML_ENTITIES[229] = "&aring;";   // å - lowercase a, ring
        HTML_ENTITIES[230] = "&aelig;";   // æ - lowercase ae
        HTML_ENTITIES[231] = "&ccedil;";   // ç - lowercase c, cedilla
        HTML_ENTITIES[232] = "&egrave;";   // è - lowercase e, grave accent
        HTML_ENTITIES[233] = "&eacute;";   // é - lowercase e, acute accent
        HTML_ENTITIES[234] = "&ecirc;";   // ê - lowercase e, circumflex accent
        HTML_ENTITIES[235] = "&euml;";   // ë - lowercase e, umlaut
        HTML_ENTITIES[236] = "&igrave;";   // ì - lowercase i, grave accent
        HTML_ENTITIES[237] = "&iacute;";   // í - lowercase i, acute accent
        HTML_ENTITIES[238] = "&icirc;";   // î - lowercase i, circumflex accent
        HTML_ENTITIES[239] = "&iuml;";   // ï - lowercase i, umlaut
        HTML_ENTITIES[240] = "&eth;";   // ð - lowercase eth, Icelandic
        HTML_ENTITIES[241] = "&ntilde;";   // ñ - lowercase n, tilde
        HTML_ENTITIES[242] = "&ograve;";   // ò - lowercase o, grave accent
        HTML_ENTITIES[243] = "&oacute;";   // ó - lowercase o, acute accent
        HTML_ENTITIES[244] = "&ocirc;";   // ô - lowercase o, circumflex accent
        HTML_ENTITIES[245] = "&otilde;";   // õ - lowercase o, tilde
        HTML_ENTITIES[246] = "&ouml;";   // ö - lowercase o, umlaut
        HTML_ENTITIES[247] = "&divide;";   // division sign
        HTML_ENTITIES[248] = "&oslash;";   // ø - lowercase o, slash
        HTML_ENTITIES[249] = "&ugrave;";   // ù - lowercase u, grave accent
        HTML_ENTITIES[250] = "&uacute;";   // ú - lowercase u, acute accent
        HTML_ENTITIES[251] = "&ucirc;";   // û - lowercase u, circumflex accent
        HTML_ENTITIES[252] = "&uuml;";   // ü - lowercase u, umlaut
        HTML_ENTITIES[253] = "&yacute;";   // ý - lowercase y, acute accent
        HTML_ENTITIES[254] = "&thorn;";   // þ - lowercase thorn, Icelandic
        HTML_ENTITIES[255] = "&yuml;";   // ÿ - lowercase y, umlaut
        // http://www.w3.org/TR/REC-html40/sgml/entities.html
        // <!-- Latin Extended-B -->
        HTML_ENTITIES[402] = "&fnof;";   //latin small f with hook = function= florin, U+0192 ISOtech -->
        // <!-- Greek -->
        HTML_ENTITIES[913] = "&Alpha;";   //greek capital letter alpha, U+0391 -->
        HTML_ENTITIES[914] = "&Beta;";   //greek capital letter beta, U+0392 -->
        HTML_ENTITIES[915] = "&Gamma;";   //greek capital letter gamma,U+0393 ISOgrk3 -->
        HTML_ENTITIES[916] = "&Delta;";   //greek capital letter delta,U+0394 ISOgrk3 -->
        HTML_ENTITIES[917] = "&Epsilon;";   //greek capital letter epsilon, U+0395 -->
        HTML_ENTITIES[918] = "&Zeta;";   //greek capital letter zeta, U+0396 -->
        HTML_ENTITIES[919] = "&Eta;";   //greek capital letter eta, U+0397 -->
        HTML_ENTITIES[920] = "&Theta;";   //greek capital letter theta,U+0398 ISOgrk3 -->
        HTML_ENTITIES[921] = "&Iota;";   //greek capital letter iota, U+0399 -->
        HTML_ENTITIES[922] = "&Kappa;";   //greek capital letter kappa, U+039A -->
        HTML_ENTITIES[923] = "&Lambda;";   //greek capital letter lambda,U+039B ISOgrk3 -->
        HTML_ENTITIES[924] = "&Mu;";   //greek capital letter mu, U+039C -->
        HTML_ENTITIES[925] = "&Nu;";   //greek capital letter nu, U+039D -->
        HTML_ENTITIES[926] = "&Xi;";   //greek capital letter xi, U+039E ISOgrk3 -->
        HTML_ENTITIES[927] = "&Omicron;";   //greek capital letter omicron, U+039F -->
        HTML_ENTITIES[928] = "&Pi;";   //greek capital letter pi, U+03A0 ISOgrk3 -->
        HTML_ENTITIES[929] = "&Rho;";   //greek capital letter rho, U+03A1 -->
        // <!-- there is no Sigmaf, and no U+03A2 character either -->
        HTML_ENTITIES[931] = "&Sigma;";   //greek capital letter sigma,U+03A3 ISOgrk3 -->
        HTML_ENTITIES[932] = "&Tau;";   //greek capital letter tau, U+03A4 -->
        HTML_ENTITIES[933] = "&Upsilon;";   //greek capital letter upsilon,U+03A5 ISOgrk3 -->
        HTML_ENTITIES[934] = "&Phi;";   //greek capital letter phi,U+03A6 ISOgrk3 -->
        HTML_ENTITIES[935] = "&Chi;";   //greek capital letter chi, U+03A7 -->
        HTML_ENTITIES[936] = "&Psi;";   //greek capital letter psi,U+03A8 ISOgrk3 -->
        HTML_ENTITIES[937] = "&Omega;";   //greek capital letter omega,U+03A9 ISOgrk3 -->
        HTML_ENTITIES[945] = "&alpha;";   //greek small letter alpha,U+03B1 ISOgrk3 -->
        HTML_ENTITIES[946] = "&beta;";   //greek small letter beta, U+03B2 ISOgrk3 -->
        HTML_ENTITIES[947] = "&gamma;";   //greek small letter gamma,U+03B3 ISOgrk3 -->
        HTML_ENTITIES[948] = "&delta;";   //greek small letter delta,U+03B4 ISOgrk3 -->
        HTML_ENTITIES[949] = "&epsilon;";   //greek small letter epsilon,U+03B5 ISOgrk3 -->
        HTML_ENTITIES[950] = "&zeta;";   //greek small letter zeta, U+03B6 ISOgrk3 -->
        HTML_ENTITIES[951] = "&eta;";   //greek small letter eta, U+03B7 ISOgrk3 -->
        HTML_ENTITIES[952] = "&theta;";   //greek small letter theta,U+03B8 ISOgrk3 -->
        HTML_ENTITIES[953] = "&iota;";   //greek small letter iota, U+03B9 ISOgrk3 -->
        HTML_ENTITIES[954] = "&kappa;";   //greek small letter kappa,U+03BA ISOgrk3 -->
        HTML_ENTITIES[955] = "&lambda;";   //greek small letter lambda,U+03BB ISOgrk3 -->
        HTML_ENTITIES[956] = "&mu;";   //greek small letter mu, U+03BC ISOgrk3 -->
        HTML_ENTITIES[957] = "&nu;";   //greek small letter nu, U+03BD ISOgrk3 -->
        HTML_ENTITIES[958] = "&xi;";   //greek small letter xi, U+03BE ISOgrk3 -->
        HTML_ENTITIES[959] = "&omicron;";   //greek small letter omicron, U+03BF NEW -->
        HTML_ENTITIES[960] = "&pi;";   //greek small letter pi, U+03C0 ISOgrk3 -->
        HTML_ENTITIES[961] = "&rho;";   //greek small letter rho, U+03C1 ISOgrk3 -->
        HTML_ENTITIES[962] = "&sigmaf;";   //greek small letter final sigma,U+03C2 ISOgrk3 -->
        HTML_ENTITIES[963] = "&sigma;";   //greek small letter sigma,U+03C3 ISOgrk3 -->
        HTML_ENTITIES[964] = "&tau;";   //greek small letter tau, U+03C4 ISOgrk3 -->
        HTML_ENTITIES[965] = "&upsilon;";   //greek small letter upsilon,U+03C5 ISOgrk3 -->
        HTML_ENTITIES[966] = "&phi;";   //greek small letter phi, U+03C6 ISOgrk3 -->
        HTML_ENTITIES[967] = "&chi;";   //greek small letter chi, U+03C7 ISOgrk3 -->
        HTML_ENTITIES[968] = "&psi;";   //greek small letter psi, U+03C8 ISOgrk3 -->
        HTML_ENTITIES[969] = "&omega;";   //greek small letter omega,U+03C9 ISOgrk3 -->
        HTML_ENTITIES[977] = "&thetasym;";   //greek small letter theta symbol,U+03D1 NEW -->
        HTML_ENTITIES[978] = "&upsih;";   //greek upsilon with hook symbol,U+03D2 NEW -->
        HTML_ENTITIES[982] = "&piv;";   //greek pi symbol, U+03D6 ISOgrk3 -->
        // <!-- General Punctuation -->
        HTML_ENTITIES[8226] = "&bull;";   //bullet = black small circle,U+2022 ISOpub  -->
        // <!-- bullet is NOT the same as bullet operator, U+2219 -->
        HTML_ENTITIES[8230] = "&hellip;";   //horizontal ellipsis = three dot leader,U+2026 ISOpub  -->
        HTML_ENTITIES[8242] = "&prime;";   //prime = minutes = feet, U+2032 ISOtech -->
        HTML_ENTITIES[8243] = "&Prime;";   //double prime = seconds = inches,U+2033 ISOtech -->
        HTML_ENTITIES[8254] = "&oline;";   //overline = spacing overscore,U+203E NEW -->
        HTML_ENTITIES[8260] = "&frasl;";   //fraction slash, U+2044 NEW -->
        // <!-- Letterlike Symbols -->
        HTML_ENTITIES[8472] = "&weierp;";   //script capital P = power set= Weierstrass p, U+2118 ISOamso -->
        HTML_ENTITIES[8465] = "&image;";   //blackletter capital I = imaginary part,U+2111 ISOamso -->
        HTML_ENTITIES[8476] = "&real;";   //blackletter capital R = real part symbol,U+211C ISOamso -->
        HTML_ENTITIES[8482] = "&trade;";   //trade mark sign, U+2122 ISOnum -->
        HTML_ENTITIES[8501] = "&alefsym;";   //alef symbol = first transfinite cardinal,U+2135 NEW -->
        // <!-- alef symbol is NOT the same as hebrew letter alef,U+05D0 although the same glyph could be used to depict both characters -->
        // <!-- Arrows -->
        HTML_ENTITIES[8592] = "&larr;";   //leftwards arrow, U+2190 ISOnum -->
        HTML_ENTITIES[8593] = "&uarr;";   //upwards arrow, U+2191 ISOnum-->
        HTML_ENTITIES[8594] = "&rarr;";   //rightwards arrow, U+2192 ISOnum -->
        HTML_ENTITIES[8595] = "&darr;";   //downwards arrow, U+2193 ISOnum -->
        HTML_ENTITIES[8596] = "&harr;";   //left right arrow, U+2194 ISOamsa -->
        HTML_ENTITIES[8629] = "&crarr;";   //downwards arrow with corner leftwards= carriage return, U+21B5 NEW -->
        HTML_ENTITIES[8656] = "&lArr;";   //leftwards double arrow, U+21D0 ISOtech -->
        // <!-- ISO 10646 does not say that lArr is the same as the 'is implied by' arrowbut also does not have any other character for that function. So ? lArr canbe used for 'is implied by' as ISOtech suggests -->
        HTML_ENTITIES[8657] = "&uArr;";   //upwards double arrow, U+21D1 ISOamsa -->
        HTML_ENTITIES[8658] = "&rArr;";   //rightwards double arrow,U+21D2 ISOtech -->
        // <!-- ISO 10646 does not say this is the 'implies' character but does not have another character with this function so ?rArr can be used for 'implies' as ISOtech suggests -->
        HTML_ENTITIES[8659] = "&dArr;";   //downwards double arrow, U+21D3 ISOamsa -->
        HTML_ENTITIES[8660] = "&hArr;";   //left right double arrow,U+21D4 ISOamsa -->
        // <!-- Mathematical Operators -->
        HTML_ENTITIES[8704] = "&forall;";   //for all, U+2200 ISOtech -->
        HTML_ENTITIES[8706] = "&part;";   //partial differential, U+2202 ISOtech  -->
        HTML_ENTITIES[8707] = "&exist;";   //there exists, U+2203 ISOtech -->
        HTML_ENTITIES[8709] = "&empty;";   //empty set = null set = diameter,U+2205 ISOamso -->
        HTML_ENTITIES[8711] = "&nabla;";   //nabla = backward difference,U+2207 ISOtech -->
        HTML_ENTITIES[8712] = "&isin;";   //element of, U+2208 ISOtech -->
        HTML_ENTITIES[8713] = "&notin;";   //not an element of, U+2209 ISOtech -->
        HTML_ENTITIES[8715] = "&ni;";   //contains as member, U+220B ISOtech -->
        // <!-- should there be a more memorable name than 'ni'? -->
        HTML_ENTITIES[8719] = "&prod;";   //n-ary product = product sign,U+220F ISOamsb -->
        // <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though the same glyph might be used for both -->
        HTML_ENTITIES[8721= "&sum;";   //n-ary summation, U+2211 ISOamsb -->
        // <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma' though the same glyph might be used for both -->
        HTML_ENTITIES[8722= "&minus;";   //minus sign, U+2212 ISOtech -->
        HTML_ENTITIES[8727= "&lowast;";   //asterisk operator, U+2217 ISOtech -->
        HTML_ENTITIES[8730= "&radic;";   //square root = radical sign,U+221A ISOtech -->
        HTML_ENTITIES[8733= "&prop;";   //proportional to, U+221D ISOtech -->
        HTML_ENTITIES[8734= "&infin;";   //infinity, U+221E ISOtech -->
        HTML_ENTITIES[8736] = "&ang;";   //angle, U+2220 ISOamso -->
        HTML_ENTITIES[8743] = "&and;";   //logical and = wedge, U+2227 ISOtech -->
        HTML_ENTITIES[8744] = "&or;";   //logical or = vee, U+2228 ISOtech -->
        HTML_ENTITIES[8745] = "&cap;";   //intersection = cap, U+2229 ISOtech -->
        HTML_ENTITIES[8746] = "&cup;";   //union = cup, U+222A ISOtech -->
        HTML_ENTITIES[8747] = "&int;";   //integral, U+222B ISOtech -->
        HTML_ENTITIES[8756] = "&there4;";   //therefore, U+2234 ISOtech -->
        HTML_ENTITIES[8764] = "&sim;";   //tilde operator = varies with = similar to,U+223C ISOtech -->
        // <!-- tilde operator is NOT the same character as the tilde, U+007E,although the same glyph might be used to represent both  -->
        HTML_ENTITIES[8773] = "&cong;";   //approximately equal to, U+2245 ISOtech -->
        HTML_ENTITIES[8776] = "&asymp;";   //almost equal to = asymptotic to,U+2248 ISOamsr -->
        HTML_ENTITIES[8800] = "&ne;";   //not equal to, U+2260 ISOtech -->
        HTML_ENTITIES[8801] = "&equiv;";   //identical to, U+2261 ISOtech -->
        HTML_ENTITIES[8804] = "&le;";   //less-than or equal to, U+2264 ISOtech -->
        HTML_ENTITIES[8805] = "&ge;";   //greater-than or equal to,U+2265 ISOtech -->
        HTML_ENTITIES[8834] = "&sub;";   //subset of, U+2282 ISOtech -->
        HTML_ENTITIES[8835] = "&sup;";   //superset of, U+2283 ISOtech -->
        // <!-- note that nsup, 'not a superset of, U+2283' is not covered by the Symbol font encoding and is not included. Should it be, for symmetry?It is in ISOamsn  --> <!ENTITY nsub"; 8836   //not a subset of, U+2284 ISOamsn -->
        HTML_ENTITIES[8838] = "&sube;";   //subset of or equal to, U+2286 ISOtech -->
        HTML_ENTITIES[8839] = "&supe;";   //superset of or equal to,U+2287 ISOtech -->
        HTML_ENTITIES[8853] = "&oplus;";   //circled plus = direct sum,U+2295 ISOamsb -->
        HTML_ENTITIES[8855] = "&otimes;";   //circled times = vector product,U+2297 ISOamsb -->
        HTML_ENTITIES[8869] = "&perp;";   //up tack = orthogonal to = perpendicular,U+22A5 ISOtech -->
        HTML_ENTITIES[8901] = "&sdot;";   //dot operator, U+22C5 ISOamsb -->
        // <!-- dot operator is NOT the same character as U+00B7 middle dot -->
        // <!-- Miscellaneous Technical -->
        HTML_ENTITIES[8968] = "&lceil;";   //left ceiling = apl upstile,U+2308 ISOamsc  -->
        HTML_ENTITIES[8969] = "&rceil;";   //right ceiling, U+2309 ISOamsc  -->
        HTML_ENTITIES[8970] = "&lfloor;";   //left floor = apl downstile,U+230A ISOamsc  -->
        HTML_ENTITIES[8971] = "&rfloor;";   //right floor, U+230B ISOamsc  -->
        HTML_ENTITIES[9001] = "&lang;";   //left-pointing angle bracket = bra,U+2329 ISOtech -->
        // <!-- lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation mark' -->
        HTML_ENTITIES[9002= "&rang;";   //right-pointing angle bracket = ket,U+232A ISOtech -->
        // <!-- rang is NOT the same character as U+003E 'greater than' or U+203A 'single right-pointing angle quotation mark' -->
        // <!-- Geometric Shapes -->
        HTML_ENTITIES[9674] = "&loz;";   //lozenge, U+25CA ISOpub -->
        // <!-- Miscellaneous Symbols -->
        HTML_ENTITIES[9824] = "&spades;";   //black spade suit, U+2660 ISOpub -->
        // <!-- black here seems to mean filled as opposed to hollow -->
        HTML_ENTITIES[9827] = "&clubs;";   //black club suit = shamrock,U+2663 ISOpub -->
        HTML_ENTITIES[9829] = "&hearts;";   //black heart suit = valentine,U+2665 ISOpub -->
        HTML_ENTITIES[9830] = "&diams;";   //black diamond suit, U+2666 ISOpub -->
        // <!-- Latin Extended-A -->
        HTML_ENTITIES[338] = "&OElig;";   //  -- latin capital ligature OE,U+0152 ISOlat2 -->
        HTML_ENTITIES[339] = "&oelig;";   //  -- latin small ligature oe, U+0153 ISOlat2 -->
        // <!-- ligature is a misnomer, this is a separate character in some languages -->
        HTML_ENTITIES[352] = "&Scaron;";   //  -- latin capital letter S with caron,U+0160 ISOlat2 -->
        HTML_ENTITIES[353] = "&scaron;";   //  -- latin small letter s with caron,U+0161 ISOlat2 -->
        HTML_ENTITIES[376] = "&Yuml;";   //  -- latin capital letter Y with diaeresis,U+0178 ISOlat2 -->
        // <!-- Spacing Modifier Letters -->
        HTML_ENTITIES[710] = "&circ;";   //  -- modifier letter circumflex accent,U+02C6 ISOpub -->
        HTML_ENTITIES[732] = "&tilde;";   //small tilde, U+02DC ISOdia -->
        // <!-- General Punctuation -->
        HTML_ENTITIES[8194] = "&ensp;";   //en space, U+2002 ISOpub -->
        HTML_ENTITIES[8195] = "&emsp;";   //em space, U+2003 ISOpub -->
        HTML_ENTITIES[8201] = "&thinsp;";   //thin space, U+2009 ISOpub -->
        HTML_ENTITIES[8204] = "&zwnj;";   //zero width non-joiner,U+200C NEW RFC 2070 -->
        HTML_ENTITIES[8205] = "&zwj;";   //zero width joiner, U+200D NEW RFC 2070 -->
        HTML_ENTITIES[8206] = "&lrm;";   //left-to-right mark, U+200E NEW RFC 2070 -->
        HTML_ENTITIES[8207] = "&rlm;";   //right-to-left mark, U+200F NEW RFC 2070 -->
        HTML_ENTITIES[8211] = "&ndash;";   //en dash, U+2013 ISOpub -->
        HTML_ENTITIES[8212] = "&mdash;";   //em dash, U+2014 ISOpub -->
        HTML_ENTITIES[8216] = "&lsquo;";   //left single quotation mark,U+2018 ISOnum -->
        HTML_ENTITIES[8217] = "&rsquo;";   //right single quotation mark,U+2019 ISOnum -->
        HTML_ENTITIES[8218] = "&sbquo;";   //single low-9 quotation mark, U+201A NEW -->
        HTML_ENTITIES[8220] = "&ldquo;";   //left double quotation mark,U+201C ISOnum -->
        HTML_ENTITIES[8221] = "&rdquo;";   //right double quotation mark,U+201D ISOnum -->
        HTML_ENTITIES[8222] = "&bdquo;";   //double low-9 quotation mark, U+201E NEW -->
        HTML_ENTITIES[8224] = "&dagger;";   //dagger, U+2020 ISOpub -->
        HTML_ENTITIES[8225] = "&Dagger;";   //double dagger, U+2021 ISOpub -->
        HTML_ENTITIES[8240] = "&permil;";   //per mille sign, U+2030 ISOtech -->
        HTML_ENTITIES[8249] = "&lsaquo;";   //single left-pointing angle quotation mark,U+2039 ISO proposed -->
        // <!-- lsaquo is proposed but not yet ISO standardized -->
        HTML_ENTITIES[8250] = "&rsaquo;";   //single right-pointing angle quotation mark,U+203A ISO proposed -->
        // <!-- rsaquo is proposed but not yet ISO standardized -->
        HTML_ENTITIES[8364] = "&euro;";   //  -- euro sign, U+20AC NEW -->
    };

    // --------------------------------------------------------- Public Methods

    /**
     * Perform an auto post redirect to the specified target using the given
     * response. If the params Map is defined then the form will post these
     * values as name value pairs. If the compress value is true, this method
     * will attempt to gzip compress the response content if requesting
     * browser accepts "gzip" encoding.
     * <p/>
     * Once this method has returned you should not attempt to write to the
     * servlet response.
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param target the target URL to send the auto post redirect to
     * @param params the map of parameter values to post
     * @param compress the flag to specify whether to attempt gzip compression
     *         of the response content
     */
    public static void autoPostRedirect(HttpServletRequest request,
            HttpServletResponse response, String target, Map params,
            boolean compress) {

        Validate.notNull(request, "Null response parameter");
        Validate.notNull(response, "Null response parameter");
        Validate.notNull(target, "Null target parameter");

        HtmlStringBuffer buffer = new HtmlStringBuffer(1024);
        buffer.append("<html><body onload=\"document.forms[0].submit();\">");
        buffer.append("<form name=\"form\" method=\"post\" style=\"{display:none;}\" action=\"");
        buffer.append(target);
        buffer.append("\">");
        for (Iterator i = params.keySet().iterator(); i.hasNext();) {
            String name = i.next().toString();
            String value = params.get(name).toString();
            buffer.elementStart("textarea");
            buffer.appendAttribute("name", name);
            buffer.elementEnd();
            buffer.append(value);
            buffer.elementEnd("textarea");
        }
        buffer.append("</form></body></html>");

        // Determine whether browser will accept gzip compression
        if (compress) {
            compress = false;
            Enumeration e = request.getHeaders("Accept-Encoding");

            while (e.hasMoreElements()) {
                String name = (String) e.nextElement();
                if (name.indexOf("gzip") != -1) {
                    compress = true;
                    break;
                }
            }
        }

        OutputStream os = null;
        GZIPOutputStream gos = null;
        try {
            response.setContentType("text/html");

            if (compress) {
                response.setHeader("Content-Encoding", "gzip");

                os = response.getOutputStream();
                gos = new GZIPOutputStream(os);
                gos.write(buffer.toString().getBytes());

            } else {
                response.setContentLength(buffer.length());

                os = response.getOutputStream();
                os.write(buffer.toString().getBytes());
            }

        } catch (IOException ex) {
            ex.printStackTrace();

        } finally {
            ClickUtils.close(gos);
            ClickUtils.close(os);
        }
    }

    /**
     * Return a new XML Document for the given input stream.
     *
     * @param inputStream the input stream
     * @return new XML Document
     * @throws RuntimeException if a parsing error occurs
     */
    public static Document buildDocument(InputStream inputStream) {
        return buildDocument(inputStream, null);
    }

    /**
     * Return a new XML Document for the given input stream and XML entity
     * resolver.
     *
     * @param inputStream the input stream
     * @param entityResolver the XML entity resolver
     * @return new XML Document
     * @throws RuntimeException if a parsing error occurs
     */
    public static Document buildDocument(InputStream inputStream,
                                         EntityResolver entityResolver) {
         try {
             DocumentBuilderFactory factory =
                 DocumentBuilderFactory.newInstance();

             DocumentBuilder builder = factory.newDocumentBuilder();

             if (entityResolver != null) {
                 builder.setEntityResolver(entityResolver);
             }

             return builder.parse(inputStream);

         } catch (Exception ex) {
             throw new RuntimeException("Error parsing XML", ex);
         }
    }

    /**
     * Returns the <code>Class</code> object associated with the class or
     * interface with the given string name, using the current Thread context
     * class loader.
     *
     * @param classname the name of the class to load
     * @return the <tt>Class</tt> object
     * @throws ClassNotFoundException if the class cannot be located
     */
    public static Class classForName(String classname)
            throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        return Class.forName(classname, true, classLoader);
    }

    /**
     * Close the given input stream and ignore any exceptions thrown.
     *
     * @param stream the stream, reader or writer to close.
     */
    public static void close(InputStream  stream) {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException ex) {
                // Ignore.
            }
        }
    }

    /**
     * Close the given output stream and ignore any exceptions thrown.
     *
     * @param stream the output stream to close.
     */
    public static void close(OutputStream stream) {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException ex) {
                // Ignore.
            }
        }
    }

    /**
     * Close the given reader and ignore any exceptions thrown.
     *
     * @param reader the reader to close.
     */
    public static void close(Reader reader) {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException ioe) {
                // Ignore
            }
        }
    }

    /**
     * Create an HTML import statement from the given string pattern and
     * versionIndicator, formatted with the request context path.
     * <p/>
     * <b>Remember</b>: the version indicator will only be added in
     * <tt>production</tt> and <tt>profile</tt> modes, and only if the
     * request attribute {@link #ENABLE_RESOURCE_VERSION}
     * is set to <tt>"true"</tt>.
     *
     * @param pattern the HTML import pattern string to format
     * @param context the request context
     * @return the formatted HTML import statement
     */
    public static String createHtmlImport(String pattern, Context context) {
        Object[] args = {
            context.getRequest().getContextPath(),
            getResourceVersionIndicator(context)
        };

        return MessageFormat.format(pattern, args);
    }

    /**
     * Creates a template model of key/value pairs which can be used by template
     * engines such as Velocity and Freemarker.
     * <p/>
     * The following objects will be added to the model:
     * <ul>
     * <li>the Page {@link org.apache.click.Page#model model} Map key/value
     * pairs
     * </li>
     * <li>context - the Servlet context path, e.g. <span class="">/mycorp</span>
     * </li>
     * <li>format - the Page {@link Format} object for formatting the display
     * of objects.
     * </li>
     * <li>messages - the {@link MessagesMap} adaptor for the
     * {@link org.apache.click.Page#getMessages()} method.
     * </li>
     * <li>path - the {@link org.apache.click.Page#path path} of the <tt>page</tt>
     * template.
     * </li>
     * <li>request - the page {@link javax.servlet.http.HttpServletRequest}
     * object.
     * </li>
     * <li>response - the page {@link javax.servlet.http.HttpServletResponse}
     * object.
     * </li>
     * <li>session - the {@link SessionMap} adaptor for the users
     * {@link javax.servlet.http.HttpSession}.
     * </li>
     * </ul>
     *
     * @param page the page to populate the template model from
     * @param context the request context
     * @return a template model as a map
     */
    public static Map createTemplateModel(final Page page, Context context) {

        ConfigService configService = ClickUtils.getConfigService(context.getServletContext());
        LogService logger = configService.getLogService();

        final Map model = new HashMap(page.getModel());

        final HttpServletRequest request = context.getRequest();

        Object pop = model.put("request", request);
        if (pop != null && !page.isStateful()) {
            String msg = page.getClass().getName() + " on " + page.getPath()
                         + " model contains an object keyed with reserved "
                         + "name \"request\". The page model object "
                         + pop + " has been replaced with the request object";
            logger.warn(msg);
        }

        pop = model.put("response", context.getResponse());
        if (pop != null && !page.isStateful()) {
            String msg = page.getClass().getName() + " on " + page.getPath()
                         + " model contains an object keyed with reserved "
                         + "name \"response\". The page model object "
                         + pop + " has been replaced with the response object";
            logger.warn(msg);
        }

        SessionMap sessionMap = new SessionMap(request.getSession(false));
        pop = model.put("session", sessionMap);
        if (pop != null && !page.isStateful()) {
            String msg = page.getClass().getName() + " on " + page.getPath()
                         + " model contains an object keyed with reserved "
                         + "name \"session\". The page model object "
                         + pop + " has been replaced with the request "
                         + " session";
            logger.warn(msg);
        }

        pop = model.put("context", request.getContextPath());
        if (pop != null && !page.isStateful()) {
            String msg = page.getClass().getName() + " on " + page.getPath()
                         + " model contains an object keyed with reserved "
                         + "name \"context\". The page model object "
                         + pop + " has been replaced with the request "
                         + " context path";
            logger.warn(msg);
        }

        Format format = page.getFormat();
        if (format != null) {
            pop = model.put("format", format);
            if (pop != null && !page.isStateful()) {
                String msg = page.getClass().getName() + " on "
                        + page.getPath()
                        + " model contains an object keyed with reserved "
                        + "name \"format\". The page model object " + pop
                        + " has been replaced with the format object";
                logger.warn(msg);
            }
        }

        String path = page.getPath();
        if (path != null) {
           pop = model.put("path", path);
            if (pop != null && !page.isStateful()) {
                String msg = page.getClass().getName() + " on "
                        + page.getPath()
                        + " model contains an object keyed with reserved "
                        + "name \"path\". The page model object " + pop
                        + " has been replaced with the page path";
                logger.warn(msg);
            }
        }

        pop = model.put("messages", page.getMessages());
        if (pop != null && !page.isStateful()) {
            String msg = page.getClass().getName() + " on " + page.getPath()
                         + " model contains an object keyed with reserved "
                         + "name \"messages\". The page model object "
                         + pop + " has been replaced with the request "
                         + " messages";
            logger.warn(msg);
        }

        return model;
    }

    /**
     * Invalidate the specified cookie and delete it from the response object.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param cookieName The name of the cookie you want to delete
     * @param path of the path the cookie you want to delete
     */
    public static void invalidateCookie(HttpServletRequest request,
            HttpServletResponse response, String cookieName, String path) {

        setCookie(request, response, cookieName, null, 0, path);
    }

    /**
     * Return true if the request is a multi-part content type POST request.
     *
     * @param request the page servlet request
     * @return true if the request is a multi-part content type POST request
     */
    public static boolean isMultipartRequest(HttpServletRequest request) {
        return ServletFileUpload.isMultipartContent(request);
    }

    /**
     * Invalidate the specified cookie and delete it from the response object. Deletes only cookies mapped
     * against the root "/" path. Otherwise use
     * {@link #invalidateCookie(HttpServletRequest, HttpServletResponse, String, String)}
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @see #invalidateCookie(HttpServletRequest, HttpServletResponse, String, String)
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param cookieName The name of the cookie you want to delete.
     */
    public static void invalidateCookie(HttpServletRequest request,
            HttpServletResponse response, String cookieName) {

        invalidateCookie(request, response, cookieName, "/");
    }

    /**
     * Return a resource bundle using the specified base name.
     *
     * @param baseName the base name of the resource bundle, a fully qualified class name
     * @return a resource bundle for the given base name
     * @throws MissingResourceException if no resource bundle for the specified base name can be found
     */
    public static ResourceBundle getBundle(String baseName) {
        return getBundle(baseName, Locale.getDefault());
    }

    /**
     * Return a resource bundle using the specified base name and locale.
     *
     * @param baseName the base name of the resource bundle, a fully qualified class name
     * @param locale the locale for which a resource bundle is desired
     * @return a resource bundle for the given base name and locale
     * @throws MissingResourceException if no resource bundle for the specified base name can be found
     */
    public static ResourceBundle getBundle(String baseName, Locale locale) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        return ResourceBundle.getBundle(baseName, locale, classLoader);
    }

    /**
     * Return the first XML child Element for the given parent Element and child
     * Element name.
     *
     * @param parent the parent element to get the child from
     * @param name the name of the child element
     * @return the first child element for the given name and parent
     */
    public static Element getChild(Element parent, String name) {
        NodeList nodeList = parent.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                if (node.getNodeName().equals(name)) {
                    return (Element) node;
                }
            }
        }
        return null;
    }

    /**
     * Return the list of XML child Element elements with the given name from
     * the given parent Element.
     *
     * @param parent the parent element to get the child from
     * @param name the name of the child element
     * @return the list of XML child elements for the given name
     */
    public static List getChildren(Element parent, String name) {
        List list = new ArrayList();
        NodeList nodeList = parent.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                if (node.getNodeName().equals(name)) {
                    list.add(node);
                }
            }
        }
        return list;
    }

    /**
     * Return the InputStream for the Click configuration file <tt>click.xml</tt>.
     * This method will first lookup the <tt>click.xml</tt> under the
     * applications <tt>WEB-INF</tt> directory, and then if not found it will
     * attempt to find the configuration file on the classpath root.
     *
     * @param servletContext the servlet context to obtain the Click configuration
     *     from
     * @return the InputStream for the Click configuration file
     * @throws RuntimeException if the resource could not be found
     */
    public static InputStream getClickConfig(ServletContext servletContext) {
        InputStream inputStream =
            servletContext.getResourceAsStream(DEFAULT_APP_CONFIG);

        if (inputStream == null) {
            inputStream = ClickUtils.getResourceAsStream("/click.xml", ClickUtils.class);
            if (inputStream == null) {
                String msg =
                    "could not find click app configuration file: "
                    + DEFAULT_APP_CONFIG + " or click.xml on classpath";
                throw new RuntimeException(msg);
            }
        }

        return inputStream;
    }

    /**
     * Return the application configuration service instance from the given
     * servlet context.
     *
     * @param servletContext the servlet context to get the config service instance
     * @return the application config service instance
     */
    public static ConfigService getConfigService(ServletContext servletContext) {
        ConfigService configService = (ConfigService)
            servletContext.getAttribute(ConfigService.CONTEXT_NAME);

        if (configService != null) {
            return configService;

        } else {
            String msg =
                "could not find ConfigService in the SerlvetContext under the"
                + " name '" + ConfigService.CONTEXT_NAME + "'.\nThis can occur"
                + " if ClickUtils.getConfigService() is called before"
                + " ClickServlet is initialized by the servlet container.\n"
                + "To fix ensure that ClickServlet is loaded at startup by"
                + " editing your web.xml and setting the load-on-startup to 0:\n\n"
                + " <servlet>\n"
                + "   <servlet-name>ClickServlet</servlet-name>\n"
                + "   <servlet-class>org.apache.click.ClickServlet</servlet-class>\n"
                + "   <load-on-startup>0</load-on-startup>\n"
                + " </servlet>\n";

            throw new RuntimeException(msg);
        }
    }

    /**
     * Returns the specified Cookie object, or null if the cookie does not exist.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param request the servlet request
     * @param name the name of the cookie
     * @return the Cookie object if it exists, otherwise null
     */
    public static Cookie getCookie(HttpServletRequest request, String name) {
        Cookie cookies[] = request.getCookies();

        if (cookies == null || name == null || name.length() == 0) {
            return null;
        }

        //Otherwise, we have to do a linear scan for the cookie.
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals(name)) {
                return cookies[i];
            }
        }

        return null;
    }

    /**
     * Sets the given cookie values in the servlet response.
     * <p/>
     * This will also put the cookie in a list of cookies to send with this request's response
     * (so that in case of a redirect occurring down the chain, the first filter
     * will always try to set this cookie again)
     * <p/>
     * The cookie secure flag is set if the request is secure.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param name the cookie name
     * @param value the cookie value
     * @param maxAge the maximum age of the cookie in seconds. A negative
     * value will expire the cookie at the end of the session, while 0 will delete
     * the cookie.
     * @param path the cookie path
     * @return the Cookie object created and set in the response
     */
    public static Cookie setCookie(HttpServletRequest request, HttpServletResponse response,
            String name, String value, int maxAge, String path) {

        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath(path);
        cookie.setSecure(request.isSecure());
        response.addCookie(cookie);

        return cookie;
    }

    /**
     * Returns the value of the specified cookie as a String. If the cookie
     * does not exist, the method returns null.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param request the servlet request
     * @param name the name of the cookie
     * @return the value of the cookie, or null if the cookie does not exist.
     */
    public static String getCookieValue(HttpServletRequest request, String name) {
        Cookie cookie = getCookie(request, name);

        if (cookie != null) {
            return cookie.getValue();
        }

        return null;
    }

    /**
     * Return the Click Framework version string.
     *
     * @return the Click Framework version string
     */
    public static String getClickVersion() {
        ResourceBundle bundle = getBundle("click-control");
        return bundle.getString("click-version");
    }

    /**
     * Return the web application version string.
     *
     * @return the web application version string
     */
    public static String getApplicationVersion() {
        return applicationVersion;
    }

    /**
     * Set the web application version string.
     *
     * @param applicationVersion the web application version string
     */
    public static void setApplicationVersion(String applicationVersion) {
        ClickUtils.applicationVersion = applicationVersion;
        cachedApplicationVersionIndicator = null;
    }

    /**
     * Return Click's version indicator for static web resources
     * (eg css, js and image files) if resource versioning is active,
     * otherwise this method will return an empty string.
     * <p/>
     * Click's resource versioning becomes active under the following
     * conditions:
     * <ul>
     * <li>the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set
     * to <tt>true</tt></li>
     * <li>the application mode must be either "production" or "profile"</li>
     * </ul>
     *
     * The version indicator is based on the current Click release version.
     * For example when using Click 1.4 this method will return the string
     * <tt>"_1.4"</tt>.
     *
     * @param context the request context
     * @return a version indicator for web resources
     */
    public static String getResourceVersionIndicator(Context context) {
        if (cachedResourceVersionIndicator != null) {
            return cachedResourceVersionIndicator;
        }

        ConfigService configService = getConfigService(context.getServletContext());

        boolean isProductionModes = configService.isProductionMode()
            || configService.isProfileMode();

        if (isProductionModes
            && isEnableResourceVersion(context)) {

            cachedResourceVersionIndicator = RESOURCE_VERSION_INDICATOR;
            return cachedResourceVersionIndicator;

        } else {
            return "";
        }
    }

    /**
     * If resource versioning is active this method will return the
     * application version indicator for static web resources
     * (eg JavaScript and Css) otherwise this method will return an empty string.
     * <p/>
     * Application resource versioning becomes active under the following
     * conditions:
     * <ul>
     * <li>the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set
     * to <tt>true</tt></li>
     * <li>the application mode must be either "production" or "profile"</li>
     * </ul>
     *
     * The version indicator is based on the application version.
     * For example if the application version is 1.2 this method will
     * return the string <tt>"_1.2"</tt>.
     * <p/>
     * The application version can be set through the static method
     * {@link #setApplicationVersion(java.lang.String)}.
     *
     * @return an application version indicator for web resources
     */
    public static String getApplicationResourceVersionIndicator() {
        // Return the cached version first
        if (cachedApplicationVersionIndicator != null) {
            return cachedApplicationVersionIndicator;
        }

        // Check if the Context has been set
        if (Context.hasThreadLocalContext()) {

            Context context = Context.getThreadLocalContext();
            ConfigService configService = ClickUtils.getConfigService(context.getServletContext());

            boolean isProductionModes = configService.isProductionMode()
                || configService.isProfileMode();

            if (isProductionModes && ClickUtils.isEnableResourceVersion(context)) {
                String version = getApplicationVersion();
                if (StringUtils.isNotBlank(version)) {
                    cachedApplicationVersionIndicator = VERSION_INDICATOR_SEP
                        + version;
                    return cachedApplicationVersionIndicator;
                }
            }
        }
        return "";
    }

    /**
     * Populate the given object's attributes with the Form's field values.
     * <p/>
     * The specified Object can either be a POJO (plain old java object) or
     * a {@link java.util.Map}. If a POJO is specified, its attributes are
     * populated from  matching form fields. If a map is specified, its
     * key/value pairs are populated from matching form fields.
     *
     * @param form the Form to obtain field values from
     * @param object the object to populate with field values
     * @param debug log debug statements when populating the object
     */
    public static void copyFormToObject(Form form, Object object,
            boolean debug) {

        ContainerUtils.copyContainerToObject(form, object);
    }

    /**
     * Populate the given Form field values with the object's attributes.
     * <p/>
     * The specified Object can either be a POJO (plain old java object) or
     * a {@link java.util.Map}. If a POJO is specified, its attributes are
     * copied to matching form fields. If a map is specified, its key/value
     * pairs are copied to matching form fields.
     *
     * @param object the object to obtain attribute values from
     * @param form the Form to populate
     * @param debug log debug statements when populating the form
     */
    public static void copyObjectToForm(Object object, Form form,
            boolean debug) {

        ContainerUtils.copyObjectToContainer(object, form);
    }

    /**
     * Deploy the specified classpath resource to the given target directory
     * under the web application root directory.
     * <p/>
     * This method will <b>not</b> override any existing resources found in the
     * target directory.
     * <p/>
     * If an IOException or SecurityException occurs this method will log a
     * warning message.
     *
     * @param servletContext the web applications servlet context
     * @param resource the classpath resource name
     * @param targetDir the target directory to deploy the resource to
     */
    public static void deployFile(ServletContext servletContext,
        String resource, String targetDir) {

        if (servletContext == null) {
            throw new IllegalArgumentException("Null servletContext parameter");
        }

        if (StringUtils.isBlank(resource)) {
            String msg = "Null resource parameter not defined";
            throw new IllegalArgumentException(msg);
        }

        String realTargetDir = servletContext.getRealPath("/") + File.separator;

        if (StringUtils.isNotBlank(targetDir)) {
            realTargetDir = realTargetDir + targetDir;
        }


        LogService logger = getConfigService(servletContext).getLogService();

        try {

            // Create files deployment directory
            File directory = new File(realTargetDir);
            if (!directory.exists()) {
                if (!directory.mkdirs()) {
                    String msg =
                        "could not create deployment directory: " + directory;
                    throw new IOException(msg);
                }
            }

            String destination = resource;
            int index = resource.lastIndexOf('/');
            if (index != -1) {
                destination = resource.substring(index + 1);
            }

            destination = realTargetDir + File.separator + destination;

            File destinationFile = new File(destination);

            if (!destinationFile.exists()) {
                InputStream inputStream =
                    getResourceAsStream(resource, ClickUtils.class);

                if (inputStream != null) {
                    FileOutputStream fos = null;
                    try {
                        fos = new FileOutputStream(destinationFile);
                        byte[] buffer = new byte[1024];
                        while (true) {
                            int length = inputStream.read(buffer);
                            if (length <  0) {
                                break;
                            }
                            fos.write(buffer, 0, length);
                        }

                        if (logger.isTraceEnabled()) {
                            int lastIndex =
                                destination.lastIndexOf(File.separatorChar);
                            if (lastIndex != -1) {
                                destination =
                                    destination.substring(lastIndex + 1);
                            }
                            String msg =
                                "deployed " + targetDir + "/" + destination;
                            logger.trace(msg);
                        }

                    } finally {
                        close(fos);
                        close(inputStream);
                    }
                } else {
                    String msg =
                        "could not locate classpath resource: " + resource;
                    throw new IOException(msg);
                }
            }

        } catch (IOException ioe) {
            String msg =
                "error occured deploying resource " + resource
                + ", error " + ioe;
            logger.warn(msg);

        } catch (SecurityException se) {
            String msg =
                "error occured deploying resource " + resource
                + ", error " + se;
            logger.warn(msg);
        }
    }

    /**
     * Deploy the specified classpath resources to the given target directory
     * under the web application root directory.
     *
     * @param servletContext the web applications servlet context
     * @param resources the array of classpath resource names
     * @param targetDir the target directory to deploy the resource to
     */
    public static void deployFiles(ServletContext servletContext,
            String[] resources, String targetDir) {

        if (resources == null) {
            throw new IllegalArgumentException("Null resources parameter");
        }

        for (int i = 0; i < resources.length; i++) {
            ClickUtils.deployFile(servletContext,
                                  resources[i],
                                  targetDir);
        }
    }

    /**
     * Deploys required files (from a file list) for a control that repsects a specific convention.
     * <p/>
     * <b>Convention:</b>
     * <p/>
     * There's a descriptor file generated by the <code>tools/standalone/dev-tasks/ListFilesTask</code>.
     * The files to deploy are all in a subdirectory placed in the same directory with the control.
     * See documentation for more details. <p/>
     *
     * <b>Usage:</b><p/>
     * In your Control simply use the code below, and everything should work automatically.
     * <pre class="prettyprint">
     * public void onDeploy(ServletContext servletContext) {
     *    ClickUtils.deployFileList(servletContext, HeavyControl.class, "click");
     * } </pre>
     *
     * @param servletContext the web applications servlet context
     * @param controlClass the class of the Control that has files for deployment
     * @param targetDir target directory where to deploy the files to. In most cases this
     * is only the reserved directory <code>click</code>
     */
    public static void deployFileList(ServletContext servletContext, Class controlClass, String targetDir) {

        String packageName = ClassUtils.getPackageName(controlClass);
        packageName = StringUtils.replaceChars(packageName, '.', '/');
        packageName = "/" + packageName;
        String controlName = ClassUtils.getShortClassName(controlClass);

        ConfigService configService = getConfigService(servletContext);
        LogService logService = configService.getLogService();
        String descriptorFile = packageName + "/" + controlName + ".files";
        logService.debug("Use deployment descriptor file:" + descriptorFile);

        try {
            InputStream is = getResourceAsStream(descriptorFile, ClickUtils.class);
            List fileList = IOUtils.readLines(is);
            if (fileList == null || fileList.isEmpty()) {
                logService.info("there are no files to deploy for control " + controlClass.getName());
                return;
            }

            // a target dir list is required cause the ClickUtils.deployFile() is too inflexible to autodetect
            // required subdirectories.
            List targetDirList = new ArrayList(fileList.size());
            for (int i = 0; i < fileList.size(); i++) {
                String filePath = (String) fileList.get(i);
                String destination = "";
                int index = filePath.lastIndexOf('/');
                if (index != -1) {
                    destination = filePath.substring(0, index + 1);
                }
                targetDirList.add(i, targetDir + "/" + destination);
                fileList.set(i, packageName + "/" + filePath);
            }

            for (int i = 0; i < fileList.size(); i++) {
                String source = (String) fileList.get(i);
                String targetDirName = (String) targetDirList.get(i);
                ClickUtils.deployFile(servletContext, source, targetDirName);
            }

        } catch (IOException e) {
            String msg = "error occured getting resource " + descriptorFile + ", error " + e;
            logService.warn(msg);
        }
    }

    /**
     * Return an encoded version of the <tt>Serializable</tt> object. The object
     * will be serialized, compressed and Base 64 encoded.
     *
     * @param object the object to encode
     * @return a serialized, compressed and Base 64 string encoding of the
     * given object
     * @throws IOException if an I/O error occurs
     * @throws IllegalArgumentException if the object parameter is null, or if
     *      the object is not Serializable
     */
    public static String encode(Object object) throws IOException {
        if (object == null) {
            throw new IllegalArgumentException("null object parameter");
        }
        if (!(object instanceof Serializable)) {
            throw new IllegalArgumentException("parameter not Serializable");
        }

        ByteArrayOutputStream bos = null;
        GZIPOutputStream gos = null;
        ObjectOutputStream oos = null;

        try {
            bos = new ByteArrayOutputStream();
            gos = new GZIPOutputStream(bos);
            oos = new ObjectOutputStream(gos);

            oos.writeObject(object);

        } finally {
            close(oos);
            close(gos);
            close(bos);
        }

        Base64 base64 = new Base64();

        try {
            byte[] byteData = base64.encode(bos.toByteArray());

            return new String(byteData);

        } catch (Throwable t) {
            String message =
                "error occured Base64 encoding: " + object + " : " + t;
            throw new IOException(message);
        }
    }

    /**
     * Return an object from the {@link #encode(Object)} string.
     *
     * @param string the encoded string
     * @return an object from the encoded
     * @throws ClassNotFoundException if the class could not be instantiated
     * @throws IOException if an data I/O error occurs
     */
    public static Object decode(String string)
            throws ClassNotFoundException, IOException {

        Base64 base64 = new Base64();
        byte[] byteData = null;

        try {
            byteData = base64.decode(string.getBytes());

        } catch (Throwable t) {
            String message =
                "error occured Base64 decoding: " + string + " : " + t;
            throw new IOException(message);
        }

        ByteArrayInputStream bis = null;
        GZIPInputStream gis = null;
        ObjectInputStream ois = null;
        try {
            bis = new ByteArrayInputStream(byteData);
            gis = new GZIPInputStream(bis);
            ois = new ObjectInputStream(gis);

            return ois.readObject();

        } finally {
            close(ois);
            close(gis);
            close(bis);
        }
    }

    /**
     * Builds a cookie string containing a username and password.
     * <p/>
     * Note: with open source this is not really secure, but it prevents users
     * from snooping the cookie file of others and by changing the XOR mask and
     * character offsets, you can easily tweak results.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param username the username
     * @param password the password
     * @param xorMask the XOR mask to encrypt the value with, must be same as
     *      as the value used to decrypt the cookie password
     * @return String encoding the input parameters, an empty string if one of
     *      the arguments equals <code>null</code>
     */
    public static String encodePasswordCookie(String username, String password, int xorMask) {
        String encoding = new String(new char[]{DELIMITER, ENCODE_CHAR_OFFSET1, ENCODE_CHAR_OFFSET2});

        return encodePasswordCookie(username, password, encoding, xorMask);
    }

    /**
     * Builds a cookie string containing a username and password, using offsets
     * to customize the encoding.
     * <p/>
     * Note: with open source this is not really secure, but it prevents users
     * from snooping the cookie file of others and by changing the XOR mask and
     * character offsets, you can easily tweak results.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param username the username
     * @param password the password
     * @param encoding a String used to customize cookie encoding (only the first 3 characters are used)
     * @param xorMask the XOR mask to encrypt the value with, must be same as
     *      as the value used to decrypt the cookie password
     * @return String encoding the input parameters, an empty string if one of
     *      the arguments equals <code>null</code>.
     */
    public static String encodePasswordCookie(String username, String password, String encoding, int xorMask) {
        StringBuffer buf = new StringBuffer();

        if (username != null && password != null) {

            char offset1 = (encoding != null && encoding.length() > 1)
                ? encoding.charAt(1) : ENCODE_CHAR_OFFSET1;

            char offset2 = (encoding != null && encoding.length() > 2)
                ? encoding.charAt(2) : ENCODE_CHAR_OFFSET2;

            byte[] bytes = (username + DELIMITER + password).getBytes();
            int b;

            for (int n = 0; n < bytes.length; n++) {
                b = bytes[n] ^ (xorMask + n);
                buf.append((char) (offset1 + (b & 0x0F)));
                buf.append((char) (offset2 + ((b >> 4) & 0x0F)));
            }
        }

        return buf.toString();
    }

    /**
     * Decodes a cookie string containing a username and password.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param cookieVal the encoded cookie username and password value
     * @param xorMask the XOR mask to decrypt the value with, must be same as
     *      as the value used to encrypt the cookie password
     * @return String[] containing the username at index 0 and the password at
     *      index 1, or <code>{ null, null }</code> if cookieVal equals
     *      <code>null</code> or the empty string.
     */
    public static String[] decodePasswordCookie(String cookieVal, int xorMask) {
        String encoding = new String(new char[]{DELIMITER, ENCODE_CHAR_OFFSET1, ENCODE_CHAR_OFFSET2});

        return decodePasswordCookie(cookieVal, encoding, xorMask);
    }

    /**
     * Decodes a cookie string containing a username and password.
     * <p/>
     * This method was derived from Atlassian <tt>CookieUtils</tt> method of
     * the same name, release under the BSD License.
     *
     * @param cookieVal the encoded cookie username and password value
     * @param encoding  a String used to customize cookie encoding (only the first 3 characters are used)
     *      - should be the same string you used to encode the cookie!
     * @param xorMask the XOR mask to decrypt the value with, must be same as
     *      as the value used to encrypt the cookie password
     * @return String[] containing the username at index 0 and the password at
     *      index 1, or <code>{ null, null }</code> if cookieVal equals
     *      <code>null</code> or the empty string.
     */
    public static String[] decodePasswordCookie(String cookieVal, String encoding,
            int xorMask) {

        // Check that the cookie value isn't null or zero-length
        if (cookieVal == null || cookieVal.length() <= 0) {
            return null;
        }

        char offset1 = (encoding != null && encoding.length() > 1)
            ? encoding.charAt(1) : ENCODE_CHAR_OFFSET1;

        char offset2 = (encoding != null && encoding.length() > 2)
            ? encoding.charAt(2) : ENCODE_CHAR_OFFSET2;

        // Decode the cookie value
        char[] chars = cookieVal.toCharArray();
        byte[] bytes = new byte[chars.length / 2];
        int b;

        for (int n = 0, m = 0; n < bytes.length; n++) {
            b = chars[m++] - offset1;
            b |= (chars[m++] - offset2) << 4;
            bytes[n] = (byte) (b ^ (xorMask + n));
        }

        cookieVal = new String(bytes);
        int pos = cookieVal.indexOf(DELIMITER);

        String username = (pos < 0) ? "" : cookieVal.substring(0, pos);
        String password = (pos < 0) ? "" : cookieVal.substring(pos + 1);

        return new String[]{username, password};
    }

    /**
     * URL encode the specified value using the "UTF-8" encoding scheme.
     * <p/>
     * For example <tt>(http://host?name=value with spaces)</tt> will become
     * <tt>(http://host?name=value%20with%20spaces)</tt>.
     * <p/>
     * This method uses {@link URLEncoder#encode(java.lang.String, java.lang.String)}
     * internally.
     *
     * @param value the value to encode using "UTF-8"
     * @return an encoded URL string
     */
    public static String encodeURL(Object value) {
        if (value == null) {
            throw new IllegalArgumentException("Null object parameter");
        }

        try {
            return URLEncoder.encode(value.toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * URL decode the specified value using the "UTF-8" encoding scheme.
     * <p/>
     * For example <tt>(http://host?name=value%20with%20spaces)</tt> will become
     * <tt>(http://host?name=value with spaces)</tt>.
     * <p/>
     * This method uses {@link URLDecoder#decode(java.lang.String, java.lang.String)}
     * internally.
     *
     * @param value the value to decode using "UTF-8"
     * @return an encoded URL string
     */
    public static String decodeURL(Object value) {
        if (value == null) {
            throw new IllegalArgumentException("Null object parameter");
        }

        try {
            return URLDecoder.decode(value.toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Return an encoded URL value for the given object using the context
     * request character encoding.
     * <p/>
     * For example <tt>(http://host?name=value with spaces)</tt> will become
     * <tt>(http://host?name=value%20with%20spaces)</tt>.
     * <p/>
     * This method uses
     * {@link URLEncoder#encode(java.lang.String, java.lang.String)} internally.
     *
     * @param object the object value to encode as a URL string
     * @param context the context providing the request character encoding
     * @return an encoded URL string
     */
    public static String encodeUrl(Object object, Context context) {
        if (object == null) {
            throw new IllegalArgumentException("Null object parameter");
        }
        if (context == null) {
            throw new IllegalArgumentException("Null context parameter");
        }

        String charset = context.getRequest().getCharacterEncoding();

        try {
            if (charset == null) {
                return URLEncoder.encode(object.toString());

            } else {
                return URLEncoder.encode(object.toString(), charset);
            }

        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException(uee);
        }
    }

    /**
     * Return a HTML escaped string for the given string value.
     *
     * @param value the string value to escape
     * @return the HTML escaped string value
     */
    public static String escapeHtml(String value) {
        if (requiresHtmlEscape(value)) {

            HtmlStringBuffer buffer = new HtmlStringBuffer(value.length() * 2);

            buffer.appendEscaped(value);

            return buffer.toString();

        } else {
            return value;
        }
    }

    /**
     * Invoke the named method on the given object and return the boolean
     * result.
     *
     * @see org.apache.click.Control#setListener(Object, String)
     *
     * @param listener
     *            the object with the method to invoke
     * @param method
     *            the name of the method to invoke
     * @return true if the listener method returned true
     */
    public static boolean invokeListener(Object listener, String method) {
        if (listener == null) {
            throw new IllegalArgumentException("Null listener parameter");
        }
        if (method == null) {
            throw new IllegalArgumentException("Null method parameter");
        }

        Method targetMethod = null;
        boolean isAccessible = true;
        try {
            Class listenerClass = listener.getClass();
            targetMethod = listenerClass.getMethod(method);

            // Change accessible for annonymous inner classes public methods
            // only. Conditional checks:
            // #1 - Target method is not accessible
            // #2 - Annonomous inner classes are not public
            // #3 - Only modify public methods
            // #4 - Annonomous inner classes have no declaring class
            // #5 - Annonomous inner classes have $ in name
            if (!targetMethod.isAccessible()
                && !Modifier.isPublic(listenerClass.getModifiers())
                && Modifier.isPublic(targetMethod.getModifiers())
                && listenerClass.getDeclaringClass() == null
                && listenerClass.getName().indexOf('$') != -1) {

                isAccessible = false;
                targetMethod.setAccessible(true);
            }


            Object result = targetMethod.invoke(listener);

            if (result instanceof Boolean) {
                return ((Boolean) result).booleanValue();

            } else {
                String msg =
                    "Invalid listener method, missing boolean return type: "
                    + targetMethod;
                throw new RuntimeException(msg);
            }

        } catch (InvocationTargetException ite) {

            Throwable e = ite.getTargetException();
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;

            } else if (e instanceof Exception) {
                String msg =
                    "Exception occured invoking public method: " + targetMethod;

                throw new RuntimeException(msg, e);

            } else if (e instanceof Error) {
                String msg =
                    "Error occured invoking public method: " + targetMethod;

                throw new RuntimeException(msg, e);

            } else {
                String msg =
                    "Error occured invoking public method: " + targetMethod;

                throw new RuntimeException(msg, e);
            }

        } catch (Exception e) {
            String msg =
                "Exception occured invoking public method: " + targetMethod;

            throw new RuntimeException(msg, e);

        } finally {
            if (targetMethod != null && !isAccessible) {
                targetMethod.setAccessible(false);
            }
        }
    }

    /**
     * Return true if static web content resource versioning is enabled.
     *
     * @param context the request context
     * @return true if static web content resource versioning is enabled
     */
    public static boolean isEnableResourceVersion(Context context) {
        return "true".equals(context.getRequestAttribute(ENABLE_RESOURCE_VERSION));
    }

    /**
     * Return the value string limited to maxlength characters. If the string
     * gets curtailed, "..." is appended to it.
     * <p/>
     * Adapted from Velocity Tools Formatter.
     *
     * @param value the string value to limit the length of
     * @param maxlength the maximum string length
     * @return a length limited string
     */
    public static String limitLength(String value, int maxlength) {
        return limitLength(value, maxlength, "...");
    }

    /**
     * Return the value string limited to maxlength characters. If the string
     * gets curtailed and the suffix parameter is appended to it.
     * <p/>
     * Adapted from Velocity Tools Formatter.
     *
     * @param value the string value to limit the length of
     * @param maxlength the maximum string length
     * @param suffix the suffix to append to the length limited string
     * @return a length limited string
     */
    public static String limitLength(String value, int maxlength, String suffix) {
        String ret = value;
        if (value.length() > maxlength) {
            ret = value.substring(0, maxlength - suffix.length()) + suffix;
        }
        return ret;
    }

    /**
     * Return the application LogService instance using thread local Context
     * to perform the lookup.
     *
     * @return the application LogService instance
     */
    public static LogService getLogService() {
        Context context = Context.getThreadLocalContext();
        ServletContext servletContext = context.getServletContext();
        ConfigService configService = getConfigService(servletContext);
        LogService logService = configService.getLogService();
        return logService;
    }

    /**
     * Return the list of Fields for the given Form, including any Fields
     * contained in FieldSets. The list of returned fields will exclude any
     * <tt>Button</tt>, <tt>FieldSet</tt> or <tt>Label</tt> fields.
     *
     * @param form the form to obtain the fields from
     * @return the list of contained form fields
     */
    public static List getFormFields(Form form) {
        if (form == null) {
            throw new IllegalArgumentException("Null form parameter");
        }
        return ContainerUtils.getInputFields(form);
    }

    /**
     * Return the mime-type or content-type for the given filename.
     *
     * @param filename the filename to obtain the mime-type for
     * @return the mime-type for the given filename, or null if not found
     */
    public static String getMimeType(String filename) {
        if (filename == null) {
            throw new IllegalArgumentException("null filename parameter");
        }

        int index = filename.lastIndexOf(".");

        if (index != -1) {
            String ext = filename.substring(index + 1);

            try {
                ResourceBundle bundle = getBundle("org/apache/click/util/mime-type");

                return bundle.getString(ext.toLowerCase());

            } catch (MissingResourceException mre) {
                return null;
            }

        } else {
            return null;
        }
    }

    /**
     * Return the given control's top level parent's localized messages Map.
     * <p/>
     * This method will walk up to the control's parent page object and
     * return pages messages. If the control's top level parent is a control
     * then the parent's messages map will be returned. If the top level
     * parent is not a Page or Control instance an empty map will be returned.
     *
     * @param control the control to get the parent messages Map for
     * @return the top level parent's Map of localized messages
     */
    public static Map getParentMessages(Control control) {
        if (control == null) {
            throw new IllegalArgumentException("Null control parameter");
        }

        Object parent = control.getParent();
        if (parent == null) {
            return Collections.EMPTY_MAP;

        } else {
            while (parent != null) {
                if (parent instanceof Control) {
                    control = (Control) parent;
                    parent = control.getParent();

                    if (parent == null) {
                        return control.getMessages();
                    }

                } else if (parent instanceof Page) {
                    Page page = (Page) parent;
                    return page.getMessages();

                } else if (parent != null) {
                    // Unknown parent class
                    return Collections.EMPTY_MAP;
                }
            }
        }

        return Collections.EMPTY_MAP;
    }

    /**
     * Return the given control's top level parent's localized message for the
     * specified name.
     * <p/>
     * This method will walk up to the control's parent page object and for each
     * parent control found, look for a message of the specified name. A
     * message found in a parent control will override the message of a child
     * control.
     * <p/>
     * Given the following property files:
     * <p/>
     * MyPage.properties
     * <pre class="prettyprint">
     * myfield.label=Page </pre>
     *
     * and MyForm.properties
     * <pre class="prettyprint">
     * myfield.label=Form </pre>
     *
     * and a the following snippet:
     *
     * <pre class="prettyprint">
     * public MyPage extends Page {
     *     public void onInit() {
     *         MyForm form = new MyForm("form");
     *         TextField field = new TextField("myfield");
     *         form.add(field);
     *
     *         //1.
     *         System.out.println(ClickUtils.getParentMessage(field, "myfield.label"));
     *
     *         addControl(form);
     *
     *         //2.
     *         System.out.println(ClickUtils.getParentMessage(field, "myfield.label"));
     *     }
     * }
     * </pre>
     *
     * The first (1.) println statement above will output <tt>Form</tt> because
     * at that stage MyForm is the highest level parent of field.
     * <tt>getParentMessage</tt> will find the property <tt>myfield.label</tt>
     * in the MyForm message properties and return <tt>Form</tt>
     * <p/>
     * The second (2.) println statement will output <tt>Page</tt> as now
     * MyPage is the highest level parent. On its first pass up the hierarchy,
     * <tt>getParentMessage</tt> will find the property <tt>myfield.label</tt>
     * in the MyForm message properties and on its second pass will find the
     * same property in MyPage message properties. As MyPage is higher up the
     * hierarchy than MyForm, MyPage will override MyForm and the property value
     * will be <tt>Page</tt>.
     *
     * @param control the control to get the parent message for
     * @param name the specific property name to find
     * @return the top level parent's Map of localized messages
     */
    public static String getParentMessage(Control control, String name) {
        if (control == null) {
            throw new IllegalArgumentException("Null control parameter");
        }
        if (name == null) {
            throw new IllegalArgumentException("Null name parameter");
        }

        Object parent = control.getParent();
        if (parent == null) {
            return null;

        } else {
            String message = null;
            while (parent != null) {
                if (parent instanceof Control) {
                    control = (Control) parent;
                    if (control != null) {
                        if (control.getMessages().containsKey(name)) {
                            message = (String) control.getMessages().get(name);
                        }
                    }

                    parent = control.getParent();
                    if (parent == null) {
                        return message;
                    }

                } else if (parent instanceof Page) {
                    Page page = (Page) parent;
                    if (page.getMessages().containsKey(name)) {
                        message = (String) page.getMessages().get(name);
                    }
                    return message;

                } else if (parent != null) {
                    // Unknown parent class
                    return null;
                }
            }
        }
        return null;
    }

    /**
     * Get the parent page of the given control. This method will walk up
     * the control's parent hierarchy to find its parent page.
     *
     * @param control the control to get the parent page from
     * @return the parent page of the control
     */
    public static Page getParentPage(Control control) {
        Object parent = control.getParent();

        while (parent != null) {
            if (parent instanceof Control) {
                control = (Control) parent;
                parent = control.getParent();

            } else if (parent instanceof Page) {
                return (Page) parent;

            } else if (parent != null) {
                throw new RuntimeException("Invalid parent class");
            }
        }

        return null;
    }

    /**
     * Return an ordered map of request parameters from the given request.
     *
     * @param request the servlet request to obtain request parameters from
     * @return the ordered map of request parameters
     */
    public static Map getRequestParameterMap(HttpServletRequest request) {

        TreeMap requestParams = new TreeMap();

        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String name = paramNames.nextElement().toString();

            String[] values = request.getParameterValues(name);

            if (values.length == 1) {
                requestParams.put(name, values[0]);

            } else {
                requestParams.put(name, values);
            }
        }

        return requestParams;
    }

    /**
     * Return the page resource path from the request. For example:
     * <pre class="codeHtml">
     * <span class="blue">http://www.mycorp.com/banking/secure/login.htm</span>  ->  <span class="red">/secure/login.htm</span> </pre>
     *
     * @param request the page servlet request
     * @return the page resource path from the request
     */
    public static String getResourcePath(HttpServletRequest request) {
        // Adapted from VelocityViewServlet.handleRequest() method:

        // If we get here from RequestDispatcher.include(), getServletPath()
        // will return the original (wrong) URI requested.  The following
        // special attribute holds the correct path.  See section 8.3 of the
        // Servlet 2.3 specification.

        String path = (String)
            request.getAttribute("javax.servlet.include.servlet_path");

        // Also take into account the PathInfo stated on
        // SRV.4.4 Request Path Elements.
        String info = (String)
            request.getAttribute("javax.servlet.include.path_info");

        if (path == null) {
            path = request.getServletPath();
            info = request.getPathInfo();
        }

        if (info != null) {
            path += info;
        }

        return path;
    }

    /**
     * Return the requestURI from the request. For example:
     * <pre class="codeHtml">
     * <span class="blue">http://www.mycorp.com/banking/secure/login.htm</span>  ->  <span class="red">/banking/secure/login.htm</span> </pre>
     *
     * @param request the page servlet request
     * @return the requestURI from the request
     */
    public static String getRequestURI(HttpServletRequest request) {
        // CLK-334. Adapted from VelocityViewServlet.handleRequest() method:

        // If we get here from RequestDispatcher.include(), getServletPath()
        // will return the original (wrong) URI requested.  The following
        // special attribute holds the correct path.  See section 8.3 of the
        // Servlet 2.3 specification.

        String requestURI = (String) request.getAttribute("javax.servlet.include.request_uri");

        if (requestURI == null) {
            requestURI = request.getRequestURI();
        }

        if (requestURI != null && requestURI.endsWith(".jsp")) {
            requestURI = StringUtils.replace(requestURI, ".jsp", ".htm");
        }

        return requestURI;
    }

    /**
     * Finds a resource with a given name. This method returns null if no
     * resource with this name is found.
     * <p>
     * This method uses the current <tt>Thread</tt> context <tt>ClassLoader</tt> to find
     * the resource. If the resource is not found the class loader of the given
     * class is then used to find the resource.
     *
     * @param name the name of the resource
     * @param aClass the class lookup the resource against, if the resource is
     *     not found using the current <tt>Thread</tt> context <tt>ClassLoader</tt>.
     * @return the input stream of the resource if found or null otherwise
     */
    public static InputStream getResourceAsStream(String name, Class aClass) {
        Validate.notNull(name, "Parameter name is null");
        Validate.notNull(aClass, "Parameter aClass is null");

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        InputStream inputStream = classLoader.getResourceAsStream(name);
        if (inputStream == null) {
            inputStream = aClass.getResourceAsStream(name);
        }

        return inputStream;
    }

    /**
     * Finds a resource with a given name. This method returns null if no
     * resource with this name is found.
     * <p>
     * This method uses the current <tt>Thread</tt> context <tt>ClassLoader</tt> to find
     * the resource. If the resource is not found the class loader of the given
     * class is then used to find the resource.
     *
     * @param name the name of the resource
     * @param aClass the class lookup the resource against, if the resource is
     *     not found using the current <tt>Thread</tt> context <tt>ClassLoader</tt>.
     * @return the URL of the resource if found or null otherwise
     */
    public static URL getResource(String name, Class aClass) {
        Validate.notNull(name, "Parameter name is null");
        Validate.notNull(aClass, "Parameter aClass is null");

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        URL url = classLoader.getResource(name);
        if (url == null) {
            url = aClass.getResource(name);
        }

        return url;
    }

    /**
     * Return the getter method name for the given property name.
     *
     * @param property the property name
     * @return the getter method name for the given property name.
     */
    public static String toGetterName(String property) {
        HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3);

        buffer.append("get");
        buffer.append(Character.toUpperCase(property.charAt(0)));
        buffer.append(property.substring(1));

        return buffer.toString();
    }

    /**
     * Return the is getter method name for the given property name.
     *
     * @param property the property name
     * @return the is getter method name for the given property name.
     */
    public static String toIsGetterName(String property) {
        HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3);

        buffer.append("is");
        buffer.append(Character.toUpperCase(property.charAt(0)));
        buffer.append(property.substring(1));

        return buffer.toString();
    }

    /**
     * Return a field label string from the given field name. For example:
     * <pre class="codeHtml">
     * <span class="blue">faxNumber</span> &nbsp; -&gt; &nbsp; <span class="red">Fax Number</span> </pre>
     * <p/>
     * <b>Note</b> toLabel will return an empty String ("") if a <tt>null</tt>
     * String name is specified.
     *
     * @param name the field name
     * @return a field label string from the given field name
     */
    public static String toLabel(String name) {
        if (name == null) {
            return "";
        }

        HtmlStringBuffer buffer = new HtmlStringBuffer();

        for (int i = 0, size = name.length(); i < size; i++) {
            char aChar = name.charAt(i);

            if (i == 0) {
                buffer.append(Character.toUpperCase(aChar));

            } else {
                buffer.append(aChar);

                if (i < name.length() - 1) {
                    char nextChar = name.charAt(i + 1);
                    if (Character.isLowerCase(aChar)
                        && (Character.isUpperCase(nextChar)
                            || Character.isDigit(nextChar))) {

                        // Add space before digits or uppercase letters
                        buffer.append(" ");

                    } else if (Character.isDigit(aChar)
                        && (!Character.isDigit(nextChar))) {

                        // Add space after digits
                        buffer.append(" ");
                    }
                }
            }
        }

        return buffer.toString();
    }

    /**
     * Return an 32 char MD5 encoded string from the given plain text.
     * The returned value is MD5 hash compatible with Tomcat catalina Realm.
     * <p/>
     * Adapted from <tt>org.apache.catalina.util.MD5Encoder</tt>
     *
     * @param plaintext the plain text value to encode
     * @return encoded MD5 string
     */
    public static String toMD5Hash(String plaintext) {
        if (plaintext == null) {
            throw new IllegalArgumentException("Null plaintext parameter");
        }
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");

            md.update(plaintext.getBytes("UTF-8"));

            byte[] binaryData = md.digest();

            char[] buffer = new char[32];

            for (int i = 0; i < 16; i++) {
                int low = (int) (binaryData[i] & 0x0f);
                int high = (int) ((binaryData[i] & 0xf0) >> 4);
                buffer[i * 2] = HEXADECIMAL[high];
                buffer[i * 2 + 1] = HEXADECIMAL[low];
            }

            return new String(buffer);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Return a field name string from the given field label.
     * <p/>
     * A label of " OK do it!" is returned as "okDoIt". Any &amp;nbsp;
     * characters will also be removed.
     * <p/>
     * A label of "customerSelect" is returned as "customerSelect".
     *
     * @param label the field label or caption
     * @return a field name string from the given field label
     */
    public static String toName(String label) {
        if (label == null) {
            throw new IllegalArgumentException("Null label parameter");
        }

        boolean doneFirstLetter = false;
        boolean lastCharBlank = false;
        boolean hasWhiteSpace = (label.indexOf(' ') != -1);

        HtmlStringBuffer buffer = new HtmlStringBuffer(label.length());
        for (int i = 0, size = label.length(); i < size; i++) {
            char aChar = label.charAt(i);

            if (aChar != ' ') {
                if (Character.isJavaIdentifierPart(aChar)) {
                    if (lastCharBlank) {
                        if (doneFirstLetter) {
                            buffer.append(Character.toUpperCase(aChar));
                            lastCharBlank = false;
                        } else {
                            buffer.append(Character.toLowerCase(aChar));
                            lastCharBlank = false;
                            doneFirstLetter = true;
                        }
                    } else {
                        if (doneFirstLetter) {
                            if (hasWhiteSpace) {
                                buffer.append(Character.toLowerCase(aChar));
                            } else {
                                buffer.append(aChar);
                            }
                        } else {
                            buffer.append(Character.toLowerCase(aChar));
                            doneFirstLetter = true;
                        }
                    }
                }
            } else {
                lastCharBlank = true;
            }
        }

        return buffer.toString();
    }

    /**
     * Return the setter method name for the given property name.
     *
     * @param property the property name
     * @return the setter method name for the given property name.
     */
    public static String toSetterName(String property) {
        HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3);

        buffer.append("set");
        buffer.append(Character.toUpperCase(property.charAt(0)));
        buffer.append(property.substring(1));

        return buffer.toString();
    }

    /**
     * Returns true if Click resources (JavaScript, CSS, images etc) packaged
     * in jars can be deployed to the root directory of the webapp, false
     * otherwise.
     * <p/>
     * This method will return false in restricted environments where write
     * access to the underlying file system is disallowed. Examples where
     * write access is not allowed include the WebLogic JEE server (this can be
     * changed though) and Google App Engine.
     *
     * @param servletContext the application servlet context
     * @return true if writes are allowed, false otherwise
     */
    public static boolean isResourcesDeployable(ServletContext servletContext) {
        try {
            boolean canWrite = (servletContext.getRealPath("/") != null);
            if (!canWrite) {
                return false;
            }

            // Since Google App Engine returns a value for getRealPath, check
            // SecurityManager if writes are allowed
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkWrite("/click");
            }
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    // -------------------------------------------------------- Package Methods

    /**
     * Return true if the given character requires HTML escaping.
     *
     * @param aChar the character value to test
     * @return true if the given character requires HTML escaping
     */
    static boolean requiresEscape(char aChar) {
        int index = aChar;

        if (index < HTML_ENTITIES.length - 1) {
            return HTML_ENTITIES[index] != null;

        } else {
            return false;
        }
    }

    /**
     * Append the HTML escaped string for the given character value to the
     * buffer.
     *
     * @param aChar the character value to escape
     * @param buffer the string buffer to append the escaped value to
     * @return the HTML escaped string for the given character value
     */
    static void appendEscapeChar(char aChar, HtmlStringBuffer buffer) {
        int index = aChar;

        if (index < HTML_ENTITIES.length - 1 && HTML_ENTITIES[index] != null) {
            buffer.append(HTML_ENTITIES[index]);

        } else {
            buffer.append(aChar);
        }
    }

    /**
     * Append the HTML escaped string for the given character value to the
     * buffer.
     *
     * @param aChar the character value to escape
     * @param buffer the string buffer to append the escaped value to
     * @return the HTML escaped string for the given character value
     */
    static void appendEscapeString(String value, HtmlStringBuffer buffer) {
        char aChar;
        for (int i = 0, size = value.length(); i < size; i++) {
            aChar = value.charAt(i);
            int index = aChar;

            if (index < HTML_ENTITIES.length - 1 && HTML_ENTITIES[index] != null) {
                buffer.append(HTML_ENTITIES[index]);

            } else {
                buffer.append(aChar);
            }
        }
    }

    // -------------------------------------------------------- Private Methods

    private static void ensureObjectPathNotNull(Object object, String path) {

        final int index = path.indexOf('.');

        if (index == -1) {
            return;
        }

        try {
            String value = path.substring(0, index);
            String getterName = toGetterName(value);
            String isGetterName = toIsGetterName(value);

            Method foundMethod = null;
            Method[] methods = object.getClass().getMethods();
            for (int i = 0; i < methods.length; i++) {
                String name = methods[i].getName();
                if (name.equals(getterName)) {
                    foundMethod = methods[i];
                    break;

                } else if (name.equals(isGetterName)) {
                    foundMethod = methods[i];
                    break;
                }
            }

            if (foundMethod == null) {
                String msg =
                    "Getter method not found for path value : " + value;
                throw new RuntimeException(msg);
            }

            Object result = foundMethod.invoke(object);

            if (result == null) {
                result = foundMethod.getReturnType().newInstance();

                String setterName = toSetterName(value);
                Class[] classArgs = { foundMethod.getReturnType() };

                Method setterMethod =
                    object.getClass().getMethod(setterName, classArgs);

                Object[] objectArgs = { result };

                setterMethod.invoke(object, objectArgs);
            }

            String remainingPath = path.substring(index + 1);

            ensureObjectPathNotNull(result, remainingPath);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Return true if the given string requires HTML escaping of characters.
     *
     * @param value the string value to test
     * @return true if the given string requires HTML escaping of characters
     */
    private static boolean requiresHtmlEscape(String value) {
        if (value == null) {
            return false;
        }

        int length = value.length();
        for (int i = 0; i < length; i++) {
            if (requiresEscape(value.charAt(i))) {
                return true;
            }
        }

        return false;
    }

}
TOP

Related Classes of org.apache.click.util.ClickUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.