src/share/jaxws_classes/com/sun/xml/internal/ws/policy/privateutil/PolicyUtils.java

changeset 0
373ffda63c9a
child 637
9c07ef4934dd
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/policy/privateutil/PolicyUtils.java	Wed Apr 27 01:27:09 2016 +0800
     1.3 @@ -0,0 +1,480 @@
     1.4 +/*
     1.5 + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.  Oracle designates this
    1.11 + * particular file as subject to the "Classpath" exception as provided
    1.12 + * by Oracle in the LICENSE file that accompanied this code.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.17 + * version 2 for more details (a copy is included in the LICENSE file that
    1.18 + * accompanied this code).
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License version
    1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.23 + *
    1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    1.25 + * or visit www.oracle.com if you need additional information or have any
    1.26 + * questions.
    1.27 + */
    1.28 +
    1.29 +package com.sun.xml.internal.ws.policy.privateutil;
    1.30 +
    1.31 +import com.sun.xml.internal.ws.policy.PolicyException;
    1.32 +import java.io.Closeable;
    1.33 +import java.io.IOException;
    1.34 +import java.io.UnsupportedEncodingException;
    1.35 +import java.lang.reflect.InvocationTargetException;
    1.36 +import java.lang.reflect.Method;
    1.37 +import java.net.URL;
    1.38 +import java.util.ArrayList;
    1.39 +import java.util.Arrays;
    1.40 +import java.util.Collection;
    1.41 +import java.util.Comparator;
    1.42 +import java.util.LinkedList;
    1.43 +import java.util.List;
    1.44 +import java.util.Queue;
    1.45 +import javax.xml.namespace.QName;
    1.46 +import javax.xml.stream.XMLStreamException;
    1.47 +import javax.xml.stream.XMLStreamReader;
    1.48 +
    1.49 +/**
    1.50 + * This is a wrapper class for various utilities that may be reused within Policy API implementation.
    1.51 + * The class is not part of public Policy API. Do not use it from your client code!
    1.52 + *
    1.53 + * @author Marek Potociar
    1.54 + */
    1.55 +public final class PolicyUtils {
    1.56 +    private PolicyUtils() { }
    1.57 +
    1.58 +    public static class Commons {
    1.59 +        /**
    1.60 +         * Method returns the name of the method that is on the {@code methodIndexInStack}
    1.61 +         * position in the call stack of the current {@link Thread}.
    1.62 +         *
    1.63 +         * @param methodIndexInStack index to the call stack to get the method name for.
    1.64 +         * @return the name of the method that is on the {@code methodIndexInStack}
    1.65 +         *         position in the call stack of the current {@link Thread}.
    1.66 +         */
    1.67 +        public static String getStackMethodName(final int methodIndexInStack) {
    1.68 +            final String methodName;
    1.69 +
    1.70 +            final StackTraceElement[] stack = Thread.currentThread().getStackTrace();
    1.71 +            if (stack.length > methodIndexInStack + 1) {
    1.72 +                methodName = stack[methodIndexInStack].getMethodName();
    1.73 +            } else {
    1.74 +                methodName = "UNKNOWN METHOD";
    1.75 +            }
    1.76 +
    1.77 +            return methodName;
    1.78 +        }
    1.79 +
    1.80 +        /**
    1.81 +         * Function returns the name of the caller method for the method executing this
    1.82 +         * function.
    1.83 +         *
    1.84 +         * @return caller method name from the call stack of the current {@link Thread}.
    1.85 +         */
    1.86 +        public static String getCallerMethodName() {
    1.87 +            String result = getStackMethodName(5);
    1.88 +            if (result.equals("invoke0")) {
    1.89 +                // We are likely running on Mac OS X, which returns a shorter stack trace
    1.90 +                result = getStackMethodName(4);
    1.91 +            }
    1.92 +            return result;
    1.93 +        }
    1.94 +    }
    1.95 +
    1.96 +    public static class IO {
    1.97 +        private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.IO.class);
    1.98 +
    1.99 +        /**
   1.100 +         * If the {@code resource} is not {@code null}, this method will try to close the
   1.101 +         * {@code resource} instance and log warning about any unexpected
   1.102 +         * {@link IOException} that may occur.
   1.103 +         *
   1.104 +         * @param resource resource to be closed
   1.105 +         */
   1.106 +        public static void closeResource(Closeable resource) {
   1.107 +            if (resource != null) {
   1.108 +                try {
   1.109 +                    resource.close();
   1.110 +                } catch (IOException e) {
   1.111 +                    LOGGER.warning(LocalizationMessages.WSP_0023_UNEXPECTED_ERROR_WHILE_CLOSING_RESOURCE(resource.toString()), e);
   1.112 +                }
   1.113 +            }
   1.114 +        }
   1.115 +
   1.116 +        /**
   1.117 +         * If the {@code reader} is not {@code null}, this method will try to close the
   1.118 +         * {@code reader} instance and log warning about any unexpected
   1.119 +         * {@link IOException} that may occur.
   1.120 +         *
   1.121 +         * @param reader resource to be closed
   1.122 +         */
   1.123 +        public static void closeResource(XMLStreamReader reader) {
   1.124 +            if (reader != null) {
   1.125 +                try {
   1.126 +                    reader.close();
   1.127 +                } catch (XMLStreamException e) {
   1.128 +                    LOGGER.warning(LocalizationMessages.WSP_0023_UNEXPECTED_ERROR_WHILE_CLOSING_RESOURCE(reader.toString()), e);
   1.129 +                }
   1.130 +            }
   1.131 +        }
   1.132 +    }
   1.133 +
   1.134 +    /**
   1.135 +     * Text utilities wrapper.
   1.136 +     */
   1.137 +    public static class Text {
   1.138 +        /**
   1.139 +         * System-specific line separator character retrieved from the Java system property
   1.140 +         * <code>line.separator</code>
   1.141 +         */
   1.142 +        public final static String NEW_LINE = System.getProperty("line.separator");
   1.143 +
   1.144 +        /**
   1.145 +         * Method creates indent string consisting of as many {@code TAB} characters as specified by {@code indentLevel} parameter
   1.146 +         *
   1.147 +         * @param indentLevel indentation level
   1.148 +         * @return indentation string as specified by indentation level
   1.149 +         *
   1.150 +         */
   1.151 +        public static String createIndent(final int indentLevel) {
   1.152 +            final char[] charData = new char[indentLevel * 4];
   1.153 +            Arrays.fill(charData, ' ');
   1.154 +            return String.valueOf(charData);
   1.155 +        }
   1.156 +    }
   1.157 +
   1.158 +    public static class Comparison {
   1.159 +        /**
   1.160 +         * The comparator comapres QName objects according to their publicly accessible attributes, in the following
   1.161 +         * order of attributes:
   1.162 +         *
   1.163 +         * 1. namespace (not null String)
   1.164 +         * 2. local name (not null String)
   1.165 +         */
   1.166 +        public static final Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
   1.167 +            public int compare(final QName qn1, final QName qn2) {
   1.168 +                if (qn1 == qn2 || qn1.equals(qn2)) {
   1.169 +                    return 0;
   1.170 +                }
   1.171 +
   1.172 +                int result;
   1.173 +
   1.174 +                result = qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
   1.175 +                if (result != 0) {
   1.176 +                    return result;
   1.177 +                }
   1.178 +
   1.179 +                return qn1.getLocalPart().compareTo(qn2.getLocalPart());
   1.180 +            }
   1.181 +        };
   1.182 +
   1.183 +        /**
   1.184 +         * Compares two boolean values in the following way: {@code false < true}
   1.185 +         *
   1.186 +         * @return {@code -1} if {@code b1 < b2}, {@code 0} if {@code b1 == b2}, {@code 1} if {@code b1 > b2}
   1.187 +         */
   1.188 +        public static int compareBoolean(final boolean b1, final boolean b2) {
   1.189 +            final int i1 = (b1) ? 1 : 0;
   1.190 +            final int i2 = (b2) ? 1 : 0;
   1.191 +
   1.192 +            return i1 - i2;
   1.193 +        }
   1.194 +
   1.195 +        /**
   1.196 +         * Compares two String values, that may possibly be null in the following way: {@code null < "string value"}
   1.197 +         *
   1.198 +         * @return {@code -1} if {@code s1 < s2}, {@code 0} if {@code s1 == s2}, {@code 1} if {@code s1 > s2}
   1.199 +         */
   1.200 +        public static int compareNullableStrings(final String s1, final String s2) {
   1.201 +            return ((s1 == null) ? ((s2 == null) ? 0 : -1) : ((s2 == null) ? 1 : s1.compareTo(s2)));
   1.202 +        }
   1.203 +    }
   1.204 +
   1.205 +    public static class Collections {
   1.206 +        /**
   1.207 +         * TODO javadocs
   1.208 +         *
   1.209 +         * @param initialBase the combination base that will be present in each combination. May be {@code null} or empty.
   1.210 +         * @param options options that should be combined. May be {@code null} or empty.
   1.211 +         * @param ignoreEmptyOption flag identifies whether empty options should be ignored or whether the method should halt
   1.212 +         *        processing and return {@code null} when an empty option is encountered
   1.213 +         * @return TODO
   1.214 +         */
   1.215 +        public static <E, T extends Collection<? extends E>, U extends Collection<? extends E>> Collection<Collection<E>> combine(final U initialBase, final Collection<T> options, final boolean ignoreEmptyOption) {
   1.216 +            List<Collection<E>> combinations = null;
   1.217 +            if (options == null || options.isEmpty()) {
   1.218 +                // no combination creation needed
   1.219 +                if (initialBase != null) {
   1.220 +                    combinations = new ArrayList<Collection<E>>(1);
   1.221 +                    combinations.add(new ArrayList<E>(initialBase));
   1.222 +                }
   1.223 +                return combinations;
   1.224 +            }
   1.225 +
   1.226 +            // creating defensive and modifiable copy of the base
   1.227 +            final Collection<E> base = new LinkedList<E>();
   1.228 +            if (initialBase != null && !initialBase.isEmpty()) {
   1.229 +                base.addAll(initialBase);
   1.230 +            }
   1.231 +            /**
   1.232 +             * now we iterate over all options and build up an option processing queue:
   1.233 +             *   1. if ignoreEmptyOption flag is not set and we found an empty option, we are going to stop processing and return null. Otherwise we
   1.234 +             *      ignore the empty option.
   1.235 +             *   2. if the option has one child only, we add the child directly to the base.
   1.236 +             *   3. if there are more children in examined node, we add it to the queue for further processing and precoumpute the final size of
   1.237 +             *      resulting collection of combinations.
   1.238 +             */
   1.239 +            int finalCombinationsSize = 1;
   1.240 +            final Queue<T> optionProcessingQueue = new LinkedList<T>();
   1.241 +            for (T option : options) {
   1.242 +                final int optionSize =  option.size();
   1.243 +
   1.244 +                if (optionSize == 0) {
   1.245 +                    if (!ignoreEmptyOption) {
   1.246 +                        return null;
   1.247 +                    }
   1.248 +                } else if (optionSize == 1) {
   1.249 +                    base.addAll(option);
   1.250 +                } else {
   1.251 +                    optionProcessingQueue.offer(option);
   1.252 +                    finalCombinationsSize *= optionSize;
   1.253 +                }
   1.254 +            }
   1.255 +
   1.256 +            // creating final combinations
   1.257 +            combinations = new ArrayList<Collection<E>>(finalCombinationsSize);
   1.258 +            combinations.add(base);
   1.259 +            if (finalCombinationsSize > 1) {
   1.260 +                T processedOption;
   1.261 +                while ((processedOption = optionProcessingQueue.poll()) != null) {
   1.262 +                    final int actualSemiCombinationCollectionSize = combinations.size();
   1.263 +                    final int newSemiCombinationCollectionSize = actualSemiCombinationCollectionSize * processedOption.size();
   1.264 +
   1.265 +                    int semiCombinationIndex = 0;
   1.266 +                    for (E optionElement : processedOption) {
   1.267 +                        for (int i = 0; i < actualSemiCombinationCollectionSize; i++) {
   1.268 +                            final Collection<E> semiCombination = combinations.get(semiCombinationIndex); // unfinished combination
   1.269 +
   1.270 +                            if (semiCombinationIndex + actualSemiCombinationCollectionSize < newSemiCombinationCollectionSize) {
   1.271 +                                // this is not the last optionElement => we create a new combination copy for the next child
   1.272 +                                combinations.add(new LinkedList<E>(semiCombination));
   1.273 +                            }
   1.274 +
   1.275 +                            semiCombination.add(optionElement);
   1.276 +                            semiCombinationIndex++;
   1.277 +                        }
   1.278 +                    }
   1.279 +                }
   1.280 +            }
   1.281 +            return combinations;
   1.282 +        }
   1.283 +    }
   1.284 +
   1.285 +    /**
   1.286 +     * Reflection utilities wrapper
   1.287 +     */
   1.288 +    static class Reflection {
   1.289 +        private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.Reflection.class);
   1.290 +
   1.291 +        /**
   1.292 +         * Reflectively invokes specified method on the specified target
   1.293 +         */
   1.294 +        static <T> T invoke(final Object target, final String methodName,
   1.295 +                final Class<T> resultClass, final Object... parameters) throws RuntimePolicyUtilsException {
   1.296 +            Class[] parameterTypes;
   1.297 +            if (parameters != null && parameters.length > 0) {
   1.298 +                parameterTypes = new Class[parameters.length];
   1.299 +                int i = 0;
   1.300 +                for (Object parameter : parameters) {
   1.301 +                    parameterTypes[i++] = parameter.getClass();
   1.302 +                }
   1.303 +            } else {
   1.304 +                parameterTypes = null;
   1.305 +            }
   1.306 +
   1.307 +            return invoke(target, methodName, resultClass, parameters, parameterTypes);
   1.308 +        }
   1.309 +
   1.310 +        /**
   1.311 +         * Reflectively invokes specified method on the specified target
   1.312 +         */
   1.313 +        public static <T> T invoke(final Object target, final String methodName, final Class<T> resultClass,
   1.314 +                final Object[] parameters, final Class[] parameterTypes) throws RuntimePolicyUtilsException {
   1.315 +            try {
   1.316 +                final Method method = target.getClass().getMethod(methodName, parameterTypes);
   1.317 +                final Object result = MethodUtil.invoke(target, method,parameters);
   1.318 +
   1.319 +                return resultClass.cast(result);
   1.320 +            } catch (IllegalArgumentException e) {
   1.321 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
   1.322 +            } catch (InvocationTargetException e) {
   1.323 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
   1.324 +            } catch (IllegalAccessException e) {
   1.325 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e.getCause()));
   1.326 +            } catch (SecurityException e) {
   1.327 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
   1.328 +            } catch (NoSuchMethodException e) {
   1.329 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
   1.330 +            }
   1.331 +        }
   1.332 +
   1.333 +        private static String createExceptionMessage(final Object target, final Object[] parameters, final String methodName) {
   1.334 +            return LocalizationMessages.WSP_0061_METHOD_INVOCATION_FAILED(target.getClass().getName(), methodName,
   1.335 +                    parameters == null ? null : Arrays.asList(parameters).toString());
   1.336 +        }
   1.337 +    }
   1.338 +
   1.339 +    public static class ConfigFile {
   1.340 +        /**
   1.341 +         * Generates a config file resource name from provided config file identifier.
   1.342 +         * The generated file name can be transformed into a URL instance using
   1.343 +         * {@link #loadFromContext(String, Object)} or {@link #loadFromClasspath(String)}
   1.344 +         * method.
   1.345 +         *
   1.346 +         * @param configFileIdentifier the string used to generate the config file URL that will be parsed. Each WSIT config
   1.347 +         *        file is in form of <code>wsit-<i>{configFileIdentifier}</i>.xml</code>. Must not be {@code null}.
   1.348 +         * @return generated config file resource name
   1.349 +         * @throw PolicyException If configFileIdentifier is null.
   1.350 +         */
   1.351 +        public static String generateFullName(final String configFileIdentifier) throws PolicyException {
   1.352 +            if (configFileIdentifier != null) {
   1.353 +                final StringBuffer buffer = new StringBuffer("wsit-");
   1.354 +                buffer.append(configFileIdentifier).append(".xml");
   1.355 +                return buffer.toString();
   1.356 +            } else {
   1.357 +                throw new PolicyException(LocalizationMessages.WSP_0080_IMPLEMENTATION_EXPECTED_NOT_NULL());
   1.358 +            }
   1.359 +        }
   1.360 +
   1.361 +        /**
   1.362 +         * Returns a URL pointing to the given config file. The file name is
   1.363 +         * looked up as a resource from a ServletContext.
   1.364 +         *
   1.365 +         * May return null if the file can not be found.
   1.366 +         *
   1.367 +         * @param configFileName The name of the file resource
   1.368 +         * @param context A ServletContext object. May not be null.
   1.369 +         */
   1.370 +        public static URL loadFromContext(final String configFileName, final Object context) {
   1.371 +            return Reflection.invoke(context, "getResource", URL.class, configFileName);
   1.372 +        }
   1.373 +
   1.374 +        /**
   1.375 +         * Returns a URL pointing to the given config file. The file is looked up as
   1.376 +         * a resource on the classpath.
   1.377 +         *
   1.378 +         * May return null if the file can not be found.
   1.379 +         *
   1.380 +         * @param configFileName the name of the file resource. May not be {@code null}.
   1.381 +         */
   1.382 +        public static URL loadFromClasspath(final String configFileName) {
   1.383 +            final ClassLoader cl = Thread.currentThread().getContextClassLoader();
   1.384 +            if (cl == null) {
   1.385 +                return ClassLoader.getSystemResource(configFileName);
   1.386 +            } else {
   1.387 +                return cl.getResource(configFileName);
   1.388 +            }
   1.389 +        }
   1.390 +    }
   1.391 +
   1.392 +    /**
   1.393 +     * Wrapper for ServiceFinder class which is not part of the Java SE yet.
   1.394 +     */
   1.395 +    public static class ServiceProvider {
   1.396 +        /**
   1.397 +         * Locates and incrementally instantiates the available providers of a
   1.398 +         * given service using the given class loader.
   1.399 +         * <p/>
   1.400 +         * <p> This method transforms the name of the given service class into a
   1.401 +         * provider-configuration filename as described above and then uses the
   1.402 +         * <tt>getResources</tt> method of the given class loader to find all
   1.403 +         * available files with that name.  These files are then read and parsed to
   1.404 +         * produce a list of provider-class names. Eventually each provider class is
   1.405 +         * instantiated and array of those instances is returned.
   1.406 +         * <p/>
   1.407 +         * <p> Because it is possible for extensions to be installed into a running
   1.408 +         * Java virtual machine, this method may return different results each time
   1.409 +         * it is invoked. <p>
   1.410 +         *
   1.411 +         * @param serviceClass The service's abstract service class. Must not be {@code null}.
   1.412 +         * @param loader  The class loader to be used to load provider-configuration files
   1.413 +         *                and instantiate provider classes, or <tt>null</tt> if the system
   1.414 +         *                class loader (or, failing that the bootstrap class loader) is to
   1.415 +         *                be used
   1.416 +         * @throws NullPointerException in case {@code service} input parameter is {@code null}.
   1.417 +         * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
   1.418 +         *                                   or names a provider class that cannot be found and instantiated
   1.419 +         * @see #load(Class)
   1.420 +         */
   1.421 +        public static <T> T[] load(final Class<T> serviceClass, final ClassLoader loader) {
   1.422 +            return ServiceFinder.find(serviceClass, loader).toArray();
   1.423 +        }
   1.424 +
   1.425 +        /**
   1.426 +         * Locates and incrementally instantiates the available providers of a
   1.427 +         * given service using the context class loader.  This convenience method
   1.428 +         * is equivalent to
   1.429 +         * <p/>
   1.430 +         * <pre>
   1.431 +         *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   1.432 +         *   return PolicyUtils.ServiceProvider.load(service, cl);
   1.433 +         * </pre>
   1.434 +         *
   1.435 +         * @param serviceClass The service's abstract service class. Must not be {@code null}.
   1.436 +         *
   1.437 +         * @throws NullPointerException in case {@code service} input parameter is {@code null}.
   1.438 +         * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
   1.439 +         *                                   or names a provider class that cannot be found and instantiated
   1.440 +         * @see #load(Class, ClassLoader)
   1.441 +         */
   1.442 +        public static <T> T[] load(final Class<T> serviceClass) {
   1.443 +            return ServiceFinder.find(serviceClass).toArray();
   1.444 +        }
   1.445 +    }
   1.446 +
   1.447 +    public static class Rfc2396 {
   1.448 +
   1.449 +        private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.Reflection.class);
   1.450 +
   1.451 +        // converts "hello%20world" into "hello world"
   1.452 +        public static String unquote(final String quoted) {
   1.453 +            if (null == quoted) {
   1.454 +                return null;
   1.455 +            }
   1.456 +            final byte[] unquoted = new byte[quoted.length()]; // result cannot be longer than original string
   1.457 +            int newLength = 0;
   1.458 +            char c;
   1.459 +            int hi, lo;
   1.460 +            for (int i=0; i < quoted.length(); i++) {    // iterarate over all chars in the input
   1.461 +                c = quoted.charAt(i);
   1.462 +                if ('%' == c) {                         // next escape sequence found
   1.463 +                    if ((i + 2) >= quoted.length()) {
   1.464 +                        throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted)), false);
   1.465 +                    }
   1.466 +                    hi = Character.digit(quoted.charAt(++i), 16);
   1.467 +                    lo = Character.digit(quoted.charAt(++i), 16);
   1.468 +                    if ((0 > hi) || (0 > lo)) {
   1.469 +                        throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted)), false);
   1.470 +                    }
   1.471 +                    unquoted[newLength++] = (byte) (hi * 16 + lo);
   1.472 +                } else { // regular character found
   1.473 +                    unquoted[newLength++] = (byte) c;
   1.474 +                }
   1.475 +            }
   1.476 +            try {
   1.477 +                return new String(unquoted, 0, newLength, "utf-8");
   1.478 +            } catch (UnsupportedEncodingException uee) {
   1.479 +                throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted), uee));
   1.480 +            }
   1.481 +        }
   1.482 +    }
   1.483 +}

mercurial