it = matchingOptions.iterator();
+ while (it.hasNext())
+ {
+ buf.append("'");
+ buf.append(it.next());
+ buf.append("'");
+ if (it.hasNext())
+ {
+ buf.append(", ");
+ }
+ }
+ buf.append(")");
+
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/BasicParser.java b/src/main/java/org/apache/commons/cli/BasicParser.java
new file mode 100644
index 00000000..d1c1c0e2
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/BasicParser.java
@@ -0,0 +1,51 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * The class BasicParser provides a very simple implementation of
+ * the {@link Parser#flatten(Options,String[],boolean) flatten} method.
+ *
+ * @version $Id: BasicParser.java 1443102 2013-02-06 18:12:16Z tn $
+ * @deprecated since 1.3, use the {@link DefaultParser} instead
+ */
+@Deprecated
+public class BasicParser extends Parser
+{
+ /**
+ * A simple implementation of {@link Parser}'s abstract
+ * {@link Parser#flatten(Options, String[], boolean) flatten} method.
+ *
+ * Note: options
and stopAtNonOption
+ * are not used in this flatten
method.
+ *
+ * @param options The command line {@link Options}
+ * @param arguments The command line arguments to be parsed
+ * @param stopAtNonOption Specifies whether to stop flattening
+ * when an non option is found.
+ * @return The arguments
String array.
+ */
+ @Override
+ protected String[] flatten(@SuppressWarnings("unused") Options options,
+ String[] arguments,
+ @SuppressWarnings("unused") boolean stopAtNonOption)
+ {
+ // just echo the arguments
+ return arguments;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/CommandLine.java b/src/main/java/org/apache/commons/cli/CommandLine.java
new file mode 100644
index 00000000..bac7bcd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/CommandLine.java
@@ -0,0 +1,380 @@
+/**
+ * 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.commons.cli;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Represents list of arguments parsed against a {@link Options} descriptor.
+ *
+ * It allows querying of a boolean {@link #hasOption(String opt)},
+ * in addition to retrieving the {@link #getOptionValue(String opt)}
+ * for options requiring arguments.
+ *
+ * Additionally, any left-over or unrecognized arguments,
+ * are available for further processing.
+ *
+ * @version $Id: CommandLine.java 1444365 2013-02-09 14:21:27Z tn $
+ */
+public class CommandLine implements Serializable
+{
+ /** The serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** the unrecognised options/arguments */
+ private final List args = new LinkedList();
+
+ /** the processed options */
+ private final List options = new ArrayList ();
+
+ /**
+ * Creates a command line.
+ */
+ protected CommandLine()
+ {
+ // nothing to do
+ }
+
+ /**
+ * Query to see if an option has been set.
+ *
+ * @param opt Short name of the option
+ * @return true if set, false if not
+ */
+ public boolean hasOption(String opt)
+ {
+ return options.contains(resolveOption(opt));
+ }
+
+ /**
+ * Query to see if an option has been set.
+ *
+ * @param opt character name of the option
+ * @return true if set, false if not
+ */
+ public boolean hasOption(char opt)
+ {
+ return hasOption(String.valueOf(opt));
+ }
+
+ /**
+ * Return the Object
type of this Option
.
+ *
+ * @param opt the name of the option
+ * @return the type of this Option
+ * @deprecated due to System.err message. Instead use getParsedOptionValue(String)
+ */
+ @Deprecated
+ public Object getOptionObject(String opt)
+ {
+ try
+ {
+ return getParsedOptionValue(opt);
+ }
+ catch (ParseException pe)
+ {
+ System.err.println("Exception found converting " + opt + " to desired type: " + pe.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Return a version of this Option
converted to a particular type.
+ *
+ * @param opt the name of the option
+ * @return the value parsed into a particular object
+ * @throws ParseException if there are problems turning the option value into the desired type
+ * @see PatternOptionBuilder
+ * @since 1.2
+ */
+ public Object getParsedOptionValue(String opt) throws ParseException
+ {
+ String res = getOptionValue(opt);
+ Option option = resolveOption(opt);
+
+ if (option == null || res == null)
+ {
+ return null;
+ }
+
+ return TypeHandler.createValue(res, option.getType());
+ }
+
+ /**
+ * Return the Object
type of this Option
.
+ *
+ * @param opt the name of the option
+ * @return the type of opt
+ */
+ public Object getOptionObject(char opt)
+ {
+ return getOptionObject(String.valueOf(opt));
+ }
+
+ /**
+ * Retrieve the first argument, if any, of this option.
+ *
+ * @param opt the name of the option
+ * @return Value of the argument if option is set, and has an argument,
+ * otherwise null.
+ */
+ public String getOptionValue(String opt)
+ {
+ String[] values = getOptionValues(opt);
+
+ return (values == null) ? null : values[0];
+ }
+
+ /**
+ * Retrieve the first argument, if any, of this option.
+ *
+ * @param opt the character name of the option
+ * @return Value of the argument if option is set, and has an argument,
+ * otherwise null.
+ */
+ public String getOptionValue(char opt)
+ {
+ return getOptionValue(String.valueOf(opt));
+ }
+
+ /**
+ * Retrieves the array of values, if any, of an option.
+ *
+ * @param opt string name of the option
+ * @return Values of the argument if option is set, and has an argument,
+ * otherwise null.
+ */
+ public String[] getOptionValues(String opt)
+ {
+ List values = new ArrayList();
+
+ for (Option option : options)
+ {
+ if (opt.equals(option.getOpt()) || opt.equals(option.getLongOpt()))
+ {
+ values.addAll(option.getValuesList());
+ }
+ }
+
+ return values.isEmpty() ? null : values.toArray(new String[values.size()]);
+ }
+
+ /**
+ * Retrieves the option object given the long or short option as a String
+ *
+ * @param opt short or long name of the option
+ * @return Canonicalized option
+ */
+ private Option resolveOption(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+ for (Option option : options)
+ {
+ if (opt.equals(option.getOpt()))
+ {
+ return option;
+ }
+
+ if (opt.equals(option.getLongOpt()))
+ {
+ return option;
+ }
+
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the array of values, if any, of an option.
+ *
+ * @param opt character name of the option
+ * @return Values of the argument if option is set, and has an argument,
+ * otherwise null.
+ */
+ public String[] getOptionValues(char opt)
+ {
+ return getOptionValues(String.valueOf(opt));
+ }
+
+ /**
+ * Retrieve the first argument, if any, of an option.
+ *
+ * @param opt name of the option
+ * @param defaultValue is the default value to be returned if the option
+ * is not specified
+ * @return Value of the argument if option is set, and has an argument,
+ * otherwise defaultValue
.
+ */
+ public String getOptionValue(String opt, String defaultValue)
+ {
+ String answer = getOptionValue(opt);
+
+ return (answer != null) ? answer : defaultValue;
+ }
+
+ /**
+ * Retrieve the argument, if any, of an option.
+ *
+ * @param opt character name of the option
+ * @param defaultValue is the default value to be returned if the option
+ * is not specified
+ * @return Value of the argument if option is set, and has an argument,
+ * otherwise defaultValue
.
+ */
+ public String getOptionValue(char opt, String defaultValue)
+ {
+ return getOptionValue(String.valueOf(opt), defaultValue);
+ }
+
+ /**
+ * Retrieve the map of values associated to the option. This is convenient
+ * for options specifying Java properties like -Dparam1=value1
+ * -Dparam2=value2 . The first argument of the option is the key, and
+ * the 2nd argument is the value. If the option has only one argument
+ * (-Dfoo ) it is considered as a boolean flag and the value is
+ * "true" .
+ *
+ * @param opt name of the option
+ * @return The Properties mapped by the option, never null
+ * even if the option doesn't exists
+ * @since 1.2
+ */
+ public Properties getOptionProperties(String opt)
+ {
+ Properties props = new Properties();
+
+ for (Option option : options)
+ {
+ if (opt.equals(option.getOpt()) || opt.equals(option.getLongOpt()))
+ {
+ List values = option.getValuesList();
+ if (values.size() >= 2)
+ {
+ // use the first 2 arguments as the key/value pair
+ props.put(values.get(0), values.get(1));
+ }
+ else if (values.size() == 1)
+ {
+ // no explicit value, handle it as a boolean
+ props.put(values.get(0), "true");
+ }
+ }
+ }
+
+ return props;
+ }
+
+ /**
+ * Retrieve any left-over non-recognized options and arguments
+ *
+ * @return remaining items passed in but not parsed as an array
+ */
+ public String[] getArgs()
+ {
+ String[] answer = new String[args.size()];
+
+ args.toArray(answer);
+
+ return answer;
+ }
+
+ /**
+ * Retrieve any left-over non-recognized options and arguments
+ *
+ * @return remaining items passed in but not parsed as a List
.
+ */
+ public List getArgList()
+ {
+ return args;
+ }
+
+ /**
+ * jkeyes
+ * - commented out until it is implemented properly
+ * Dump state, suitable for debugging.
+ *
+ * @return Stringified form of this object
+ */
+
+ /*
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("[ CommandLine: [ options: ");
+ buf.append(options.toString());
+ buf.append(" ] [ args: ");
+ buf.append(args.toString());
+ buf.append(" ] ]");
+
+ return buf.toString();
+ }
+ */
+
+ /**
+ * Add left-over unrecognized option/argument.
+ *
+ * @param arg the unrecognised option/argument.
+ */
+ protected void addArg(String arg)
+ {
+ args.add(arg);
+ }
+
+ /**
+ * Add an option to the command line. The values of the option are stored.
+ *
+ * @param opt the processed option
+ */
+ protected void addOption(Option opt)
+ {
+ options.add(opt);
+ }
+
+ /**
+ * Returns an iterator over the Option members of CommandLine.
+ *
+ * @return an Iterator
over the processed {@link Option}
+ * members of this {@link CommandLine}
+ */
+ public Iterator iterator()
+ {
+ return options.iterator();
+ }
+
+ /**
+ * Returns an array of the processed {@link Option}s.
+ *
+ * @return an array of the processed {@link Option}s.
+ */
+ public Option[] getOptions()
+ {
+ Collection processed = options;
+
+ // reinitialise array
+ Option[] optionsArray = new Option[processed.size()];
+
+ // return the array
+ return processed.toArray(optionsArray);
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/CommandLineParser.java b/src/main/java/org/apache/commons/cli/CommandLineParser.java
new file mode 100644
index 00000000..5b1bd833
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/CommandLineParser.java
@@ -0,0 +1,98 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * A class that implements the CommandLineParser
interface
+ * can parse a String array according to the {@link Options} specified
+ * and return a {@link CommandLine}.
+ *
+ * @version $Id: CommandLineParser.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+public interface CommandLineParser
+{
+ /**
+ * Parse the arguments according to the specified options.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @return the list of atomic option and value tokens
+ *
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ CommandLine parse(Options options, String[] arguments) throws ParseException;
+
+ /**
+ * Parse the arguments according to the specified options and
+ * properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @return the list of atomic option and value tokens
+ *
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ /* To maintain binary compatibility, this is commented out.
+ It is still in the abstract Parser class, so most users will
+ still reap the benefit.
+ CommandLine parse(Options options, String[] arguments, Properties properties)
+ throws ParseException;
+ */
+
+ /**
+ * Parse the arguments according to the specified options.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param stopAtNonOption if true an unrecognized argument stops
+ * the parsing and the remaining arguments are added to the
+ * {@link CommandLine}s args list. If false an unrecognized
+ * argument triggers a ParseException.
+ *
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException;
+
+ /**
+ * Parse the arguments according to the specified options and
+ * properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @param stopAtNonOption if true an unrecognized argument stops
+ * the parsing and the remaining arguments are added to the
+ * {@link CommandLine}s args list. If false an unrecognized
+ * argument triggers a ParseException.
+ *
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ /* To maintain binary compatibility, this is commented out.
+ It is still in the abstract Parser class, so most users will
+ still reap the benefit.
+ CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
+ throws ParseException;
+ */
+}
diff --git a/src/main/java/org/apache/commons/cli/DefaultParser.java b/src/main/java/org/apache/commons/cli/DefaultParser.java
new file mode 100644
index 00000000..4d0e0ab9
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/DefaultParser.java
@@ -0,0 +1,694 @@
+/**
+ * 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.commons.cli;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Default parser.
+ *
+ * @version $Id: DefaultParser.java 1677454 2015-05-03 17:13:54Z ggregory $
+ * @since 1.3
+ */
+public class DefaultParser implements CommandLineParser
+{
+ /** The command-line instance. */
+ protected CommandLine cmd;
+
+ /** The current options. */
+ protected Options options;
+
+ /**
+ * Flag indicating how unrecognized tokens are handled. true to stop
+ * the parsing and add the remaining tokens to the args list.
+ * false to throw an exception.
+ */
+ protected boolean stopAtNonOption;
+
+ /** The token currently processed. */
+ protected String currentToken;
+
+ /** The last option parsed. */
+ protected Option currentOption;
+
+ /** Flag indicating if tokens should no longer be analyzed and simply added as arguments of the command line. */
+ protected boolean skipParsing;
+
+ /** The required options and groups expected to be found when parsing the command line. */
+ protected List expectedOpts;
+
+ public CommandLine parse(Options options, String[] arguments) throws ParseException
+ {
+ return parse(options, arguments, null);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @return the list of atomic option and value tokens
+ *
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
+ {
+ return parse(options, arguments, properties, false);
+ }
+
+ public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
+ {
+ return parse(options, arguments, null, stopAtNonOption);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @param stopAtNonOption if true an unrecognized argument stops
+ * the parsing and the remaining arguments are added to the
+ * {@link CommandLine}s args list. If false an unrecognized
+ * argument triggers a ParseException.
+ *
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
+ throws ParseException
+ {
+ this.options = options;
+ this.stopAtNonOption = stopAtNonOption;
+ skipParsing = false;
+ currentOption = null;
+ expectedOpts = new ArrayList(options.getRequiredOptions());
+
+ // clear the data from the groups
+ for (OptionGroup group : options.getOptionGroups())
+ {
+ group.setSelected(null);
+ }
+
+ cmd = new CommandLine();
+
+ if (arguments != null)
+ {
+ for (String argument : arguments)
+ {
+ handleToken(argument);
+ }
+ }
+
+ // check the arguments of the last option
+ checkRequiredArgs();
+
+ // add the default options
+ handleProperties(properties);
+
+ checkRequiredOptions();
+
+ return cmd;
+ }
+
+ /**
+ * Sets the values of Options using the values in properties
.
+ *
+ * @param properties The value properties to be processed.
+ */
+ private void handleProperties(Properties properties) throws ParseException
+ {
+ if (properties == null)
+ {
+ return;
+ }
+
+ for (Enumeration> e = properties.propertyNames(); e.hasMoreElements();)
+ {
+ String option = e.nextElement().toString();
+
+ Option opt = options.getOption(option);
+ if (opt == null)
+ {
+ throw new UnrecognizedOptionException("Default option wasn't defined", option);
+ }
+
+ // if the option is part of a group, check if another option of the group has been selected
+ OptionGroup group = options.getOptionGroup(opt);
+ boolean selected = group != null && group.getSelected() != null;
+
+ if (!cmd.hasOption(option) && !selected)
+ {
+ // get the value from the properties
+ String value = properties.getProperty(option);
+
+ if (opt.hasArg())
+ {
+ if (opt.getValues() == null || opt.getValues().length == 0)
+ {
+ opt.addValueForProcessing(value);
+ }
+ }
+ else if (!("yes".equalsIgnoreCase(value)
+ || "true".equalsIgnoreCase(value)
+ || "1".equalsIgnoreCase(value)))
+ {
+ // if the value is not yes, true or 1 then don't add the option to the CommandLine
+ continue;
+ }
+
+ handleOption(opt);
+ currentOption = null;
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link MissingOptionException} if all of the required options
+ * are not present.
+ *
+ * @throws MissingOptionException if any of the required Options
+ * are not present.
+ */
+ private void checkRequiredOptions() throws MissingOptionException
+ {
+ // if there are required options that have not been processed
+ if (!expectedOpts.isEmpty())
+ {
+ throw new MissingOptionException(expectedOpts);
+ }
+ }
+
+ /**
+ * Throw a {@link MissingArgumentException} if the current option
+ * didn't receive the number of arguments expected.
+ */
+ private void checkRequiredArgs() throws ParseException
+ {
+ if (currentOption != null && currentOption.requiresArg())
+ {
+ throw new MissingArgumentException(currentOption);
+ }
+ }
+
+ /**
+ * Handle any command line token.
+ *
+ * @param token the command line token to handle
+ * @throws ParseException
+ */
+ private void handleToken(String token) throws ParseException
+ {
+ currentToken = token;
+
+ if (skipParsing)
+ {
+ cmd.addArg(token);
+ }
+ else if ("--".equals(token))
+ {
+ skipParsing = true;
+ }
+ else if (currentOption != null && currentOption.acceptsArg() && isArgument(token))
+ {
+ currentOption.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(token));
+ }
+ else if (token.startsWith("--"))
+ {
+ handleLongOption(token);
+ }
+ else if (token.startsWith("-") && !"-".equals(token))
+ {
+ handleShortAndLongOption(token);
+ }
+ else
+ {
+ handleUnknownToken(token);
+ }
+
+ if (currentOption != null && !currentOption.acceptsArg())
+ {
+ currentOption = null;
+ }
+ }
+
+ /**
+ * Returns true is the token is a valid argument.
+ *
+ * @param token
+ */
+ private boolean isArgument(String token)
+ {
+ return !isOption(token) || isNegativeNumber(token);
+ }
+
+ /**
+ * Check if the token is a negative number.
+ *
+ * @param token
+ */
+ private boolean isNegativeNumber(String token)
+ {
+ try
+ {
+ Double.parseDouble(token);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Tells if the token looks like an option.
+ *
+ * @param token
+ */
+ private boolean isOption(String token)
+ {
+ return isLongOption(token) || isShortOption(token);
+ }
+
+ /**
+ * Tells if the token looks like a short option.
+ *
+ * @param token
+ */
+ private boolean isShortOption(String token)
+ {
+ // short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
+ return token.startsWith("-") && token.length() >= 2 && options.hasShortOption(token.substring(1, 2));
+ }
+
+ /**
+ * Tells if the token looks like a long option.
+ *
+ * @param token
+ */
+ private boolean isLongOption(String token)
+ {
+ if (!token.startsWith("-") || token.length() == 1)
+ {
+ return false;
+ }
+
+ int pos = token.indexOf("=");
+ String t = pos == -1 ? token : token.substring(0, pos);
+
+ if (!options.getMatchingOptions(t).isEmpty())
+ {
+ // long or partial long options (--L, -L, --L=V, -L=V, --l, --l=V)
+ return true;
+ }
+ else if (getLongPrefix(token) != null && !token.startsWith("--"))
+ {
+ // -LV
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handles an unknown token. If the token starts with a dash an
+ * UnrecognizedOptionException is thrown. Otherwise the token is added
+ * to the arguments of the command line. If the stopAtNonOption flag
+ * is set, this stops the parsing and the remaining tokens are added
+ * as-is in the arguments of the command line.
+ *
+ * @param token the command line token to handle
+ */
+ private void handleUnknownToken(String token) throws ParseException
+ {
+ if (token.startsWith("-") && token.length() > 1 && !stopAtNonOption)
+ {
+ throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
+ }
+
+ cmd.addArg(token);
+ if (stopAtNonOption)
+ {
+ skipParsing = true;
+ }
+ }
+
+ /**
+ * Handles the following tokens:
+ *
+ * --L
+ * --L=V
+ * --L V
+ * --l
+ *
+ * @param token the command line token to handle
+ */
+ private void handleLongOption(String token) throws ParseException
+ {
+ if (token.indexOf('=') == -1)
+ {
+ handleLongOptionWithoutEqual(token);
+ }
+ else
+ {
+ handleLongOptionWithEqual(token);
+ }
+ }
+
+ /**
+ * Handles the following tokens:
+ *
+ * --L
+ * -L
+ * --l
+ * -l
+ *
+ * @param token the command line token to handle
+ */
+ private void handleLongOptionWithoutEqual(String token) throws ParseException
+ {
+ List matchingOpts = options.getMatchingOptions(token);
+ if (matchingOpts.isEmpty())
+ {
+ handleUnknownToken(currentToken);
+ }
+ else if (matchingOpts.size() > 1)
+ {
+ throw new AmbiguousOptionException(token, matchingOpts);
+ }
+ else
+ {
+ handleOption(options.getOption(matchingOpts.get(0)));
+ }
+ }
+
+ /**
+ * Handles the following tokens:
+ *
+ * --L=V
+ * -L=V
+ * --l=V
+ * -l=V
+ *
+ * @param token the command line token to handle
+ */
+ private void handleLongOptionWithEqual(String token) throws ParseException
+ {
+ int pos = token.indexOf('=');
+
+ String value = token.substring(pos + 1);
+
+ String opt = token.substring(0, pos);
+
+ List matchingOpts = options.getMatchingOptions(opt);
+ if (matchingOpts.isEmpty())
+ {
+ handleUnknownToken(currentToken);
+ }
+ else if (matchingOpts.size() > 1)
+ {
+ throw new AmbiguousOptionException(opt, matchingOpts);
+ }
+ else
+ {
+ Option option = options.getOption(matchingOpts.get(0));
+
+ if (option.acceptsArg())
+ {
+ handleOption(option);
+ currentOption.addValueForProcessing(value);
+ currentOption = null;
+ }
+ else
+ {
+ handleUnknownToken(currentToken);
+ }
+ }
+ }
+
+ /**
+ * Handles the following tokens:
+ *
+ * -S
+ * -SV
+ * -S V
+ * -S=V
+ * -S1S2
+ * -S1S2 V
+ * -SV1=V2
+ *
+ * -L
+ * -LV
+ * -L V
+ * -L=V
+ * -l
+ *
+ * @param token the command line token to handle
+ */
+ private void handleShortAndLongOption(String token) throws ParseException
+ {
+ String t = Util.stripLeadingHyphens(token);
+
+ int pos = t.indexOf('=');
+
+ if (t.length() == 1)
+ {
+ // -S
+ if (options.hasShortOption(t))
+ {
+ handleOption(options.getOption(t));
+ }
+ else
+ {
+ handleUnknownToken(token);
+ }
+ }
+ else if (pos == -1)
+ {
+ // no equal sign found (-xxx)
+ if (options.hasShortOption(t))
+ {
+ handleOption(options.getOption(t));
+ }
+ else if (!options.getMatchingOptions(t).isEmpty())
+ {
+ // -L or -l
+ handleLongOptionWithoutEqual(token);
+ }
+ else
+ {
+ // look for a long prefix (-Xmx512m)
+ String opt = getLongPrefix(t);
+
+ if (opt != null && options.getOption(opt).acceptsArg())
+ {
+ handleOption(options.getOption(opt));
+ currentOption.addValueForProcessing(t.substring(opt.length()));
+ currentOption = null;
+ }
+ else if (isJavaProperty(t))
+ {
+ // -SV1 (-Dflag)
+ handleOption(options.getOption(t.substring(0, 1)));
+ currentOption.addValueForProcessing(t.substring(1));
+ currentOption = null;
+ }
+ else
+ {
+ // -S1S2S3 or -S1S2V
+ handleConcatenatedOptions(token);
+ }
+ }
+ }
+ else
+ {
+ // equal sign found (-xxx=yyy)
+ String opt = t.substring(0, pos);
+ String value = t.substring(pos + 1);
+
+ if (opt.length() == 1)
+ {
+ // -S=V
+ Option option = options.getOption(opt);
+ if (option != null && option.acceptsArg())
+ {
+ handleOption(option);
+ currentOption.addValueForProcessing(value);
+ currentOption = null;
+ }
+ else
+ {
+ handleUnknownToken(token);
+ }
+ }
+ else if (isJavaProperty(opt))
+ {
+ // -SV1=V2 (-Dkey=value)
+ handleOption(options.getOption(opt.substring(0, 1)));
+ currentOption.addValueForProcessing(opt.substring(1));
+ currentOption.addValueForProcessing(value);
+ currentOption = null;
+ }
+ else
+ {
+ // -L=V or -l=V
+ handleLongOptionWithEqual(token);
+ }
+ }
+ }
+
+ /**
+ * Search for a prefix that is the long name of an option (-Xmx512m)
+ *
+ * @param token
+ */
+ private String getLongPrefix(String token)
+ {
+ String t = Util.stripLeadingHyphens(token);
+
+ int i;
+ String opt = null;
+ for (i = t.length() - 2; i > 1; i--)
+ {
+ String prefix = t.substring(0, i);
+ if (options.hasLongOption(prefix))
+ {
+ opt = prefix;
+ break;
+ }
+ }
+
+ return opt;
+ }
+
+ /**
+ * Check if the specified token is a Java-like property (-Dkey=value).
+ */
+ private boolean isJavaProperty(String token)
+ {
+ String opt = token.substring(0, 1);
+ Option option = options.getOption(opt);
+
+ return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES);
+ }
+
+ private void handleOption(Option option) throws ParseException
+ {
+ // check the previous option before handling the next one
+ checkRequiredArgs();
+
+ option = (Option) option.clone();
+
+ updateRequiredOptions(option);
+
+ cmd.addOption(option);
+
+ if (option.hasArg())
+ {
+ currentOption = option;
+ }
+ else
+ {
+ currentOption = null;
+ }
+ }
+
+ /**
+ * Removes the option or its group from the list of expected elements.
+ *
+ * @param option
+ */
+ private void updateRequiredOptions(Option option) throws AlreadySelectedException
+ {
+ if (option.isRequired())
+ {
+ expectedOpts.remove(option.getKey());
+ }
+
+ // if the option is in an OptionGroup make that option the selected option of the group
+ if (options.getOptionGroup(option) != null)
+ {
+ OptionGroup group = options.getOptionGroup(option);
+
+ if (group.isRequired())
+ {
+ expectedOpts.remove(group);
+ }
+
+ group.setSelected(option);
+ }
+ }
+
+ /**
+ * Breaks token
into its constituent parts
+ * using the following algorithm.
+ *
+ *
+ * ignore the first character ("- ")
+ * foreach remaining character check if an {@link Option}
+ * exists with that id.
+ * if an {@link Option} does exist then add that character
+ * prepended with "- " to the list of processed tokens.
+ * if the {@link Option} can have an argument value and there
+ * are remaining characters in the token then add the remaining
+ * characters as a token to the list of processed tokens.
+ * if an {@link Option} does NOT exist AND
+ * stopAtNonOption
IS set then add the special token
+ * "-- " followed by the remaining characters and also
+ * the remaining tokens directly to the processed tokens list.
+ * if an {@link Option} does NOT exist AND
+ * stopAtNonOption
IS NOT set then add that
+ * character prepended with "- ".
+ *
+ *
+ * @param token The current token to be burst
+ * at the first non-Option encountered.
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line token.
+ */
+ protected void handleConcatenatedOptions(String token) throws ParseException
+ {
+ for (int i = 1; i < token.length(); i++)
+ {
+ String ch = String.valueOf(token.charAt(i));
+
+ if (options.hasOption(ch))
+ {
+ handleOption(options.getOption(ch));
+
+ if (currentOption != null && token.length() != i + 1)
+ {
+ // add the trail as an argument of the option
+ currentOption.addValueForProcessing(token.substring(i + 1));
+ break;
+ }
+ }
+ else
+ {
+ handleUnknownToken(stopAtNonOption && i > 1 ? token.substring(i) : token);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/GnuParser.java b/src/main/java/org/apache/commons/cli/GnuParser.java
new file mode 100644
index 00000000..05def788
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/GnuParser.java
@@ -0,0 +1,115 @@
+/**
+ * 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.commons.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class GnuParser provides an implementation of the
+ * {@link Parser#flatten(Options, String[], boolean) flatten} method.
+ *
+ * @version $Id: GnuParser.java 1445352 2013-02-12 20:48:19Z tn $
+ * @deprecated since 1.3, use the {@link DefaultParser} instead
+ */
+@Deprecated
+public class GnuParser extends Parser
+{
+ /**
+ * This flatten method does so using the following rules:
+ *
+ * If an {@link Option} exists for the first character of
+ * the arguments
entry AND an {@link Option}
+ * does not exist for the whole argument
then
+ * add the first character as an option to the processed tokens
+ * list e.g. "-D" and add the rest of the entry to the also.
+ * Otherwise just add the token to the processed tokens list.
+ *
+ *
+ * @param options The Options to parse the arguments by.
+ * @param arguments The arguments that have to be flattened.
+ * @param stopAtNonOption specifies whether to stop flattening when
+ * a non option has been encountered
+ * @return a String array of the flattened arguments
+ */
+ @Override
+ protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption)
+ {
+ List tokens = new ArrayList();
+
+ boolean eatTheRest = false;
+
+ for (int i = 0; i < arguments.length; i++)
+ {
+ String arg = arguments[i];
+
+ if ("--".equals(arg))
+ {
+ eatTheRest = true;
+ tokens.add("--");
+ }
+ else if ("-".equals(arg))
+ {
+ tokens.add("-");
+ }
+ else if (arg.startsWith("-"))
+ {
+ String opt = Util.stripLeadingHyphens(arg);
+
+ if (options.hasOption(opt))
+ {
+ tokens.add(arg);
+ }
+ else
+ {
+ if (opt.indexOf('=') != -1 && options.hasOption(opt.substring(0, opt.indexOf('='))))
+ {
+ // the format is --foo=value or -foo=value
+ tokens.add(arg.substring(0, arg.indexOf('='))); // --foo
+ tokens.add(arg.substring(arg.indexOf('=') + 1)); // value
+ }
+ else if (options.hasOption(arg.substring(0, 2)))
+ {
+ // the format is a special properties option (-Dproperty=value)
+ tokens.add(arg.substring(0, 2)); // -D
+ tokens.add(arg.substring(2)); // property=value
+ }
+ else
+ {
+ eatTheRest = stopAtNonOption;
+ tokens.add(arg);
+ }
+ }
+ }
+ else
+ {
+ tokens.add(arg);
+ }
+
+ if (eatTheRest)
+ {
+ for (i++; i < arguments.length; i++) //NOPMD
+ {
+ tokens.add(arguments[i]);
+ }
+ }
+ }
+
+ return tokens.toArray(new String[tokens.size()]);
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/HelpFormatter.java b/src/main/java/org/apache/commons/cli/HelpFormatter.java
new file mode 100644
index 00000000..9d9bbddd
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/HelpFormatter.java
@@ -0,0 +1,1094 @@
+/**
+ * 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.commons.cli;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A formatter of help messages for command line options.
+ *
+ * Example:
+ *
+ *
+ * Options options = new Options();
+ * options.addOption(OptionBuilder.withLongOpt("file")
+ * .withDescription("The file to be processed")
+ * .hasArg()
+ * .withArgName("FILE")
+ * .isRequired()
+ * .create('f'));
+ * options.addOption(OptionBuilder.withLongOpt("version")
+ * .withDescription("Print the version of the application")
+ * .create('v'));
+ * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
+ *
+ * String header = "Do something useful with an input file\n\n";
+ * String footer = "\nPlease report issues at http://example.com/issues";
+ *
+ * HelpFormatter formatter = new HelpFormatter();
+ * formatter.printHelp("myapp", header, options, footer, true);
+ *
+ *
+ * This produces the following output:
+ *
+ *
+ * usage: myapp -f <FILE> [-h] [-v]
+ * Do something useful with an input file
+ *
+ * -f,--file <FILE> The file to be processed
+ * -h,--help
+ * -v,--version Print the version of the application
+ *
+ * Please report issues at http://example.com/issues
+ *
+ *
+ * @version $Id: HelpFormatter.java 1677407 2015-05-03 14:31:12Z britter $
+ */
+public class HelpFormatter
+{
+ // --------------------------------------------------------------- Constants
+
+ /** default number of characters per line */
+ public static final int DEFAULT_WIDTH = 74;
+
+ /** default padding to the left of each line */
+ public static final int DEFAULT_LEFT_PAD = 1;
+
+ /** number of space characters to be prefixed to each description line */
+ public static final int DEFAULT_DESC_PAD = 3;
+
+ /** the string to display at the beginning of the usage statement */
+ public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
+
+ /** default prefix for shortOpts */
+ public static final String DEFAULT_OPT_PREFIX = "-";
+
+ /** default prefix for long Option */
+ public static final String DEFAULT_LONG_OPT_PREFIX = "--";
+
+ /**
+ * default separator displayed between a long Option and its value
+ *
+ * @since 1.3
+ **/
+ public static final String DEFAULT_LONG_OPT_SEPARATOR = " ";
+
+ /** default name for an argument */
+ public static final String DEFAULT_ARG_NAME = "arg";
+
+ // -------------------------------------------------------------- Attributes
+
+ /**
+ * number of characters per line
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setWidth methods instead.
+ */
+ @Deprecated
+ public int defaultWidth = DEFAULT_WIDTH;
+
+ /**
+ * amount of padding to the left of each line
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setLeftPadding methods instead.
+ */
+ @Deprecated
+ public int defaultLeftPad = DEFAULT_LEFT_PAD;
+
+ /**
+ * the number of characters of padding to be prefixed
+ * to each description line
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setDescPadding methods instead.
+ */
+ @Deprecated
+ public int defaultDescPad = DEFAULT_DESC_PAD;
+
+ /**
+ * the string to display at the beginning of the usage statement
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setSyntaxPrefix methods instead.
+ */
+ @Deprecated
+ public String defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX;
+
+ /**
+ * the new line string
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setNewLine methods instead.
+ */
+ @Deprecated
+ public String defaultNewLine = System.getProperty("line.separator");
+
+ /**
+ * the shortOpt prefix
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setOptPrefix methods instead.
+ */
+ @Deprecated
+ public String defaultOptPrefix = DEFAULT_OPT_PREFIX;
+
+ /**
+ * the long Opt prefix
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setLongOptPrefix methods instead.
+ */
+ @Deprecated
+ public String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX;
+
+ /**
+ * the name of the argument
+ *
+ * @deprecated Scope will be made private for next major version
+ * - use get/setArgName methods instead.
+ */
+ @Deprecated
+ public String defaultArgName = DEFAULT_ARG_NAME;
+
+ /**
+ * Comparator used to sort the options when they output in help text
+ *
+ * Defaults to case-insensitive alphabetical sorting by option key
+ */
+ protected Comparator optionComparator = new OptionComparator();
+
+ /** The separator displayed between the long option and its value. */
+ private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR;
+
+ /**
+ * Sets the 'width'.
+ *
+ * @param width the new value of 'width'
+ */
+ public void setWidth(int width)
+ {
+ this.defaultWidth = width;
+ }
+
+ /**
+ * Returns the 'width'.
+ *
+ * @return the 'width'
+ */
+ public int getWidth()
+ {
+ return defaultWidth;
+ }
+
+ /**
+ * Sets the 'leftPadding'.
+ *
+ * @param padding the new value of 'leftPadding'
+ */
+ public void setLeftPadding(int padding)
+ {
+ this.defaultLeftPad = padding;
+ }
+
+ /**
+ * Returns the 'leftPadding'.
+ *
+ * @return the 'leftPadding'
+ */
+ public int getLeftPadding()
+ {
+ return defaultLeftPad;
+ }
+
+ /**
+ * Sets the 'descPadding'.
+ *
+ * @param padding the new value of 'descPadding'
+ */
+ public void setDescPadding(int padding)
+ {
+ this.defaultDescPad = padding;
+ }
+
+ /**
+ * Returns the 'descPadding'.
+ *
+ * @return the 'descPadding'
+ */
+ public int getDescPadding()
+ {
+ return defaultDescPad;
+ }
+
+ /**
+ * Sets the 'syntaxPrefix'.
+ *
+ * @param prefix the new value of 'syntaxPrefix'
+ */
+ public void setSyntaxPrefix(String prefix)
+ {
+ this.defaultSyntaxPrefix = prefix;
+ }
+
+ /**
+ * Returns the 'syntaxPrefix'.
+ *
+ * @return the 'syntaxPrefix'
+ */
+ public String getSyntaxPrefix()
+ {
+ return defaultSyntaxPrefix;
+ }
+
+ /**
+ * Sets the 'newLine'.
+ *
+ * @param newline the new value of 'newLine'
+ */
+ public void setNewLine(String newline)
+ {
+ this.defaultNewLine = newline;
+ }
+
+ /**
+ * Returns the 'newLine'.
+ *
+ * @return the 'newLine'
+ */
+ public String getNewLine()
+ {
+ return defaultNewLine;
+ }
+
+ /**
+ * Sets the 'optPrefix'.
+ *
+ * @param prefix the new value of 'optPrefix'
+ */
+ public void setOptPrefix(String prefix)
+ {
+ this.defaultOptPrefix = prefix;
+ }
+
+ /**
+ * Returns the 'optPrefix'.
+ *
+ * @return the 'optPrefix'
+ */
+ public String getOptPrefix()
+ {
+ return defaultOptPrefix;
+ }
+
+ /**
+ * Sets the 'longOptPrefix'.
+ *
+ * @param prefix the new value of 'longOptPrefix'
+ */
+ public void setLongOptPrefix(String prefix)
+ {
+ this.defaultLongOptPrefix = prefix;
+ }
+
+ /**
+ * Returns the 'longOptPrefix'.
+ *
+ * @return the 'longOptPrefix'
+ */
+ public String getLongOptPrefix()
+ {
+ return defaultLongOptPrefix;
+ }
+
+ /**
+ * Set the separator displayed between a long option and its value.
+ * Ensure that the separator specified is supported by the parser used,
+ * typically ' ' or '='.
+ *
+ * @param longOptSeparator the separator, typically ' ' or '='.
+ * @since 1.3
+ */
+ public void setLongOptSeparator(String longOptSeparator)
+ {
+ this.longOptSeparator = longOptSeparator;
+ }
+
+ /**
+ * Returns the separator displayed between a long option and its value.
+ *
+ * @return the separator
+ * @since 1.3
+ */
+ public String getLongOptSeparator()
+ {
+ return longOptSeparator;
+ }
+
+ /**
+ * Sets the 'argName'.
+ *
+ * @param name the new value of 'argName'
+ */
+ public void setArgName(String name)
+ {
+ this.defaultArgName = name;
+ }
+
+ /**
+ * Returns the 'argName'.
+ *
+ * @return the 'argName'
+ */
+ public String getArgName()
+ {
+ return defaultArgName;
+ }
+
+ /**
+ * Comparator used to sort the options when they output in help text.
+ * Defaults to case-insensitive alphabetical sorting by option key.
+ *
+ * @return the {@link Comparator} currently in use to sort the options
+ * @since 1.2
+ */
+ public Comparator getOptionComparator()
+ {
+ return optionComparator;
+ }
+
+ /**
+ * Set the comparator used to sort the options when they output in help text.
+ * Passing in a null comparator will keep the options in the order they were declared.
+ *
+ * @param comparator the {@link Comparator} to use for sorting the options
+ * @since 1.2
+ */
+ public void setOptionComparator(Comparator comparator)
+ {
+ this.optionComparator = comparator;
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param cmdLineSyntax the syntax for this application
+ * @param options the Options instance
+ */
+ public void printHelp(String cmdLineSyntax, Options options)
+ {
+ printHelp(getWidth(), cmdLineSyntax, null, options, null, false);
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param cmdLineSyntax the syntax for this application
+ * @param options the Options instance
+ * @param autoUsage whether to print an automatically generated
+ * usage statement
+ */
+ public void printHelp(String cmdLineSyntax, Options options, boolean autoUsage)
+ {
+ printHelp(getWidth(), cmdLineSyntax, null, options, null, autoUsage);
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param footer the banner to display at the end of the help
+ */
+ public void printHelp(String cmdLineSyntax, String header, Options options, String footer)
+ {
+ printHelp(cmdLineSyntax, header, options, footer, false);
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param footer the banner to display at the end of the help
+ * @param autoUsage whether to print an automatically generated
+ * usage statement
+ */
+ public void printHelp(String cmdLineSyntax, String header, Options options, String footer, boolean autoUsage)
+ {
+ printHelp(getWidth(), cmdLineSyntax, header, options, footer, autoUsage);
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param width the number of characters to be displayed on each line
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param footer the banner to display at the end of the help
+ */
+ public void printHelp(int width, String cmdLineSyntax, String header, Options options, String footer)
+ {
+ printHelp(width, cmdLineSyntax, header, options, footer, false);
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax. This method prints help information to
+ * System.out.
+ *
+ * @param width the number of characters to be displayed on each line
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param footer the banner to display at the end of the help
+ * @param autoUsage whether to print an automatically generated
+ * usage statement
+ */
+ public void printHelp(int width, String cmdLineSyntax, String header,
+ Options options, String footer, boolean autoUsage)
+ {
+ PrintWriter pw = new PrintWriter(System.out);
+
+ printHelp(pw, width, cmdLineSyntax, header, options, getLeftPadding(), getDescPadding(), footer, autoUsage);
+ pw.flush();
+ }
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax.
+ *
+ * @param pw the writer to which the help will be written
+ * @param width the number of characters to be displayed on each line
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param leftPad the number of characters of padding to be prefixed
+ * to each line
+ * @param descPad the number of characters of padding to be prefixed
+ * to each description line
+ * @param footer the banner to display at the end of the help
+ *
+ * @throws IllegalStateException if there is no room to print a line
+ */
+ public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
+ String header, Options options, int leftPad,
+ int descPad, String footer)
+ {
+ printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false);
+ }
+
+
+ /**
+ * Print the help for options
with the specified
+ * command line syntax.
+ *
+ * @param pw the writer to which the help will be written
+ * @param width the number of characters to be displayed on each line
+ * @param cmdLineSyntax the syntax for this application
+ * @param header the banner to display at the beginning of the help
+ * @param options the Options instance
+ * @param leftPad the number of characters of padding to be prefixed
+ * to each line
+ * @param descPad the number of characters of padding to be prefixed
+ * to each description line
+ * @param footer the banner to display at the end of the help
+ * @param autoUsage whether to print an automatically generated
+ * usage statement
+ *
+ * @throws IllegalStateException if there is no room to print a line
+ */
+ public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
+ String header, Options options, int leftPad,
+ int descPad, String footer, boolean autoUsage)
+ {
+ if (cmdLineSyntax == null || cmdLineSyntax.length() == 0)
+ {
+ throw new IllegalArgumentException("cmdLineSyntax not provided");
+ }
+
+ if (autoUsage)
+ {
+ printUsage(pw, width, cmdLineSyntax, options);
+ }
+ else
+ {
+ printUsage(pw, width, cmdLineSyntax);
+ }
+
+ if (header != null && header.trim().length() > 0)
+ {
+ printWrapped(pw, width, header);
+ }
+
+ printOptions(pw, width, options, leftPad, descPad);
+
+ if (footer != null && footer.trim().length() > 0)
+ {
+ printWrapped(pw, width, footer);
+ }
+ }
+
+ /**
+ * Prints the usage statement for the specified application.
+ *
+ * @param pw The PrintWriter to print the usage statement
+ * @param width The number of characters to display per line
+ * @param app The application name
+ * @param options The command line Options
+ */
+ public void printUsage(PrintWriter pw, int width, String app, Options options)
+ {
+ // initialise the string buffer
+ StringBuffer buff = new StringBuffer(getSyntaxPrefix()).append(app).append(" ");
+
+ // create a list for processed option groups
+ Collection processedGroups = new ArrayList();
+
+ List optList = new ArrayList (options.getOptions());
+ if (getOptionComparator() != null)
+ {
+ Collections.sort(optList, getOptionComparator());
+ }
+ // iterate over the options
+ for (Iterator it = optList.iterator(); it.hasNext();)
+ {
+ // get the next Option
+ Option option = it.next();
+
+ // check if the option is part of an OptionGroup
+ OptionGroup group = options.getOptionGroup(option);
+
+ // if the option is part of a group
+ if (group != null)
+ {
+ // and if the group has not already been processed
+ if (!processedGroups.contains(group))
+ {
+ // add the group to the processed list
+ processedGroups.add(group);
+
+
+ // add the usage clause
+ appendOptionGroup(buff, group);
+ }
+
+ // otherwise the option was displayed in the group
+ // previously so ignore it.
+ }
+
+ // if the Option is not part of an OptionGroup
+ else
+ {
+ appendOption(buff, option, option.isRequired());
+ }
+
+ if (it.hasNext())
+ {
+ buff.append(" ");
+ }
+ }
+
+
+ // call printWrapped
+ printWrapped(pw, width, buff.toString().indexOf(' ') + 1, buff.toString());
+ }
+
+ /**
+ * Appends the usage clause for an OptionGroup to a StringBuffer.
+ * The clause is wrapped in square brackets if the group is required.
+ * The display of the options is handled by appendOption
+ * @param buff the StringBuffer to append to
+ * @param group the group to append
+ * @see #appendOption(StringBuffer,Option,boolean)
+ */
+ private void appendOptionGroup(StringBuffer buff, OptionGroup group)
+ {
+ if (!group.isRequired())
+ {
+ buff.append("[");
+ }
+
+ List optList = new ArrayList (group.getOptions());
+ if (getOptionComparator() != null)
+ {
+ Collections.sort(optList, getOptionComparator());
+ }
+ // for each option in the OptionGroup
+ for (Iterator it = optList.iterator(); it.hasNext();)
+ {
+ // whether the option is required or not is handled at group level
+ appendOption(buff, it.next(), true);
+
+ if (it.hasNext())
+ {
+ buff.append(" | ");
+ }
+ }
+
+ if (!group.isRequired())
+ {
+ buff.append("]");
+ }
+ }
+
+ /**
+ * Appends the usage clause for an Option to a StringBuffer.
+ *
+ * @param buff the StringBuffer to append to
+ * @param option the Option to append
+ * @param required whether the Option is required or not
+ */
+ private void appendOption(StringBuffer buff, Option option, boolean required)
+ {
+ if (!required)
+ {
+ buff.append("[");
+ }
+
+ if (option.getOpt() != null)
+ {
+ buff.append("-").append(option.getOpt());
+ }
+ else
+ {
+ buff.append("--").append(option.getLongOpt());
+ }
+
+ // if the Option has a value and a non blank argname
+ if (option.hasArg() && (option.getArgName() == null || option.getArgName().length() != 0))
+ {
+ buff.append(option.getOpt() == null ? longOptSeparator : " ");
+ buff.append("<").append(option.getArgName() != null ? option.getArgName() : getArgName()).append(">");
+ }
+
+ // if the Option is not a required option
+ if (!required)
+ {
+ buff.append("]");
+ }
+ }
+
+ /**
+ * Print the cmdLineSyntax to the specified writer, using the
+ * specified width.
+ *
+ * @param pw The printWriter to write the help to
+ * @param width The number of characters per line for the usage statement.
+ * @param cmdLineSyntax The usage statement.
+ */
+ public void printUsage(PrintWriter pw, int width, String cmdLineSyntax)
+ {
+ int argPos = cmdLineSyntax.indexOf(' ') + 1;
+
+ printWrapped(pw, width, getSyntaxPrefix().length() + argPos, getSyntaxPrefix() + cmdLineSyntax);
+ }
+
+ /**
+ * Print the help for the specified Options to the specified writer,
+ * using the specified width, left padding and description padding.
+ *
+ * @param pw The printWriter to write the help to
+ * @param width The number of characters to display per line
+ * @param options The command line Options
+ * @param leftPad the number of characters of padding to be prefixed
+ * to each line
+ * @param descPad the number of characters of padding to be prefixed
+ * to each description line
+ */
+ public void printOptions(PrintWriter pw, int width, Options options,
+ int leftPad, int descPad)
+ {
+ StringBuffer sb = new StringBuffer();
+
+ renderOptions(sb, width, options, leftPad, descPad);
+ pw.println(sb.toString());
+ }
+
+ /**
+ * Print the specified text to the specified PrintWriter.
+ *
+ * @param pw The printWriter to write the help to
+ * @param width The number of characters to display per line
+ * @param text The text to be written to the PrintWriter
+ */
+ public void printWrapped(PrintWriter pw, int width, String text)
+ {
+ printWrapped(pw, width, 0, text);
+ }
+
+ /**
+ * Print the specified text to the specified PrintWriter.
+ *
+ * @param pw The printWriter to write the help to
+ * @param width The number of characters to display per line
+ * @param nextLineTabStop The position on the next line for the first tab.
+ * @param text The text to be written to the PrintWriter
+ */
+ public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text)
+ {
+ StringBuffer sb = new StringBuffer(text.length());
+
+ renderWrappedTextBlock(sb, width, nextLineTabStop, text);
+ pw.println(sb.toString());
+ }
+
+ // --------------------------------------------------------------- Protected
+
+ /**
+ * Render the specified Options and return the rendered Options
+ * in a StringBuffer.
+ *
+ * @param sb The StringBuffer to place the rendered Options into.
+ * @param width The number of characters to display per line
+ * @param options The command line Options
+ * @param leftPad the number of characters of padding to be prefixed
+ * to each line
+ * @param descPad the number of characters of padding to be prefixed
+ * to each description line
+ *
+ * @return the StringBuffer with the rendered Options contents.
+ */
+ protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, int descPad)
+ {
+ final String lpad = createPadding(leftPad);
+ final String dpad = createPadding(descPad);
+
+ // first create list containing only -a,--aaa where
+ // -a is opt and --aaa is long opt; in parallel look for
+ // the longest opt string this list will be then used to
+ // sort options ascending
+ int max = 0;
+ List prefixList = new ArrayList();
+
+ List optList = options.helpOptions();
+
+ if (getOptionComparator() != null)
+ {
+ Collections.sort(optList, getOptionComparator());
+ }
+
+ for (Option option : optList)
+ {
+ StringBuffer optBuf = new StringBuffer();
+
+ if (option.getOpt() == null)
+ {
+ optBuf.append(lpad).append(" ").append(getLongOptPrefix()).append(option.getLongOpt());
+ }
+ else
+ {
+ optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt());
+
+ if (option.hasLongOpt())
+ {
+ optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt());
+ }
+ }
+
+ if (option.hasArg())
+ {
+ String argName = option.getArgName();
+ if (argName != null && argName.length() == 0)
+ {
+ // if the option has a blank argname
+ optBuf.append(' ');
+ }
+ else
+ {
+ optBuf.append(option.hasLongOpt() ? longOptSeparator : " ");
+ optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">");
+ }
+ }
+
+ prefixList.add(optBuf);
+ max = optBuf.length() > max ? optBuf.length() : max;
+ }
+
+ int x = 0;
+
+ for (Iterator it = optList.iterator(); it.hasNext();)
+ {
+ Option option = it.next();
+ StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString());
+
+ if (optBuf.length() < max)
+ {
+ optBuf.append(createPadding(max - optBuf.length()));
+ }
+
+ optBuf.append(dpad);
+
+ int nextLineTabStop = max + descPad;
+
+ if (option.getDescription() != null)
+ {
+ optBuf.append(option.getDescription());
+ }
+
+ renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
+
+ if (it.hasNext())
+ {
+ sb.append(getNewLine());
+ }
+ }
+
+ return sb;
+ }
+
+ /**
+ * Render the specified text and return the rendered Options
+ * in a StringBuffer.
+ *
+ * @param sb The StringBuffer to place the rendered text into.
+ * @param width The number of characters to display per line
+ * @param nextLineTabStop The position on the next line for the first tab.
+ * @param text The text to be rendered.
+ *
+ * @return the StringBuffer with the rendered Options contents.
+ */
+ protected StringBuffer renderWrappedText(StringBuffer sb, int width,
+ int nextLineTabStop, String text)
+ {
+ int pos = findWrapPos(text, width, 0);
+
+ if (pos == -1)
+ {
+ sb.append(rtrim(text));
+
+ return sb;
+ }
+ sb.append(rtrim(text.substring(0, pos))).append(getNewLine());
+
+ if (nextLineTabStop >= width)
+ {
+ // stops infinite loop happening
+ nextLineTabStop = 1;
+ }
+
+ // all following lines must be padded with nextLineTabStop space characters
+ final String padding = createPadding(nextLineTabStop);
+
+ while (true)
+ {
+ text = padding + text.substring(pos).trim();
+ pos = findWrapPos(text, width, 0);
+
+ if (pos == -1)
+ {
+ sb.append(text);
+
+ return sb;
+ }
+
+ if (text.length() > width && pos == nextLineTabStop - 1)
+ {
+ pos = width;
+ }
+
+ sb.append(rtrim(text.substring(0, pos))).append(getNewLine());
+ }
+ }
+
+ /**
+ * Render the specified text width a maximum width. This method differs
+ * from renderWrappedText by not removing leading spaces after a new line.
+ *
+ * @param sb The StringBuffer to place the rendered text into.
+ * @param width The number of characters to display per line
+ * @param nextLineTabStop The position on the next line for the first tab.
+ * @param text The text to be rendered.
+ */
+ private Appendable renderWrappedTextBlock(StringBuffer sb, int width, int nextLineTabStop, String text)
+ {
+ try
+ {
+ BufferedReader in = new BufferedReader(new StringReader(text));
+ String line;
+ boolean firstLine = true;
+ while ((line = in.readLine()) != null)
+ {
+ if (!firstLine)
+ {
+ sb.append(getNewLine());
+ }
+ else
+ {
+ firstLine = false;
+ }
+ renderWrappedText(sb, width, nextLineTabStop, line);
+ }
+ }
+ catch (IOException e) //NOPMD
+ {
+ // cannot happen
+ }
+
+ return sb;
+ }
+
+ /**
+ * Finds the next text wrap position after startPos
for the
+ * text in text
with the column width width
.
+ * The wrap point is the last position before startPos+width having a
+ * whitespace character (space, \n, \r). If there is no whitespace character
+ * before startPos+width, it will return startPos+width.
+ *
+ * @param text The text being searched for the wrap position
+ * @param width width of the wrapped text
+ * @param startPos position from which to start the lookup whitespace
+ * character
+ * @return position on which the text must be wrapped or -1 if the wrap
+ * position is at the end of the text
+ */
+ protected int findWrapPos(String text, int width, int startPos)
+ {
+ // the line ends before the max wrap pos or a new line char found
+ int pos = text.indexOf('\n', startPos);
+ if (pos != -1 && pos <= width)
+ {
+ return pos + 1;
+ }
+
+ pos = text.indexOf('\t', startPos);
+ if (pos != -1 && pos <= width)
+ {
+ return pos + 1;
+ }
+
+ if (startPos + width >= text.length())
+ {
+ return -1;
+ }
+
+ // look for the last whitespace character before startPos+width
+ for (pos = startPos + width; pos >= startPos; --pos)
+ {
+ final char c = text.charAt(pos);
+ if (c == ' ' || c == '\n' || c == '\r')
+ {
+ break;
+ }
+ }
+
+ // if we found it - just return
+ if (pos > startPos)
+ {
+ return pos;
+ }
+
+ // if we didn't find one, simply chop at startPos+width
+ pos = startPos + width;
+
+ return pos == text.length() ? -1 : pos;
+ }
+
+ /**
+ * Return a String of padding of length len
.
+ *
+ * @param len The length of the String of padding to create.
+ *
+ * @return The String of padding
+ */
+ protected String createPadding(int len)
+ {
+ char[] padding = new char[len];
+ Arrays.fill(padding, ' ');
+
+ return new String(padding);
+ }
+
+ /**
+ * Remove the trailing whitespace from the specified String.
+ *
+ * @param s The String to remove the trailing padding from.
+ *
+ * @return The String of without the trailing padding
+ */
+ protected String rtrim(String s)
+ {
+ if (s == null || s.length() == 0)
+ {
+ return s;
+ }
+
+ int pos = s.length();
+
+ while (pos > 0 && Character.isWhitespace(s.charAt(pos - 1)))
+ {
+ --pos;
+ }
+
+ return s.substring(0, pos);
+ }
+
+ // ------------------------------------------------------ Package protected
+ // ---------------------------------------------------------------- Private
+ // ---------------------------------------------------------- Inner classes
+ /**
+ * This class implements the Comparator
interface
+ * for comparing Options.
+ */
+ private static class OptionComparator implements Comparator , Serializable
+ {
+ /** The serial version UID. */
+ private static final long serialVersionUID = 5305467873966684014L;
+
+ /**
+ * Compares its two arguments for order. Returns a negative
+ * integer, zero, or a positive integer as the first argument
+ * is less than, equal to, or greater than the second.
+ *
+ * @param opt1 The first Option to be compared.
+ * @param opt2 The second Option to be compared.
+ * @return a negative integer, zero, or a positive integer as
+ * the first argument is less than, equal to, or greater than the
+ * second.
+ */
+ public int compare(Option opt1, Option opt2)
+ {
+ return opt1.getKey().compareToIgnoreCase(opt2.getKey());
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/cli/MissingArgumentException.java b/src/main/java/org/apache/commons/cli/MissingArgumentException.java
new file mode 100644
index 00000000..99747756
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/MissingArgumentException.java
@@ -0,0 +1,71 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * Thrown when an option requiring an argument
+ * is not provided with an argument.
+ *
+ * @version $Id: MissingArgumentException.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+public class MissingArgumentException extends ParseException
+{
+ /**
+ * This exception {@code serialVersionUID}.
+ */
+ private static final long serialVersionUID = -7098538588704965017L;
+
+ /** The option requiring additional arguments */
+ private Option option;
+
+ /**
+ * Construct a new MissingArgumentException
+ * with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public MissingArgumentException(String message)
+ {
+ super(message);
+ }
+
+ /**
+ * Construct a new MissingArgumentException
+ * with the specified detail message.
+ *
+ * @param option the option requiring an argument
+ * @since 1.2
+ */
+ public MissingArgumentException(Option option)
+ {
+ this("Missing argument for option: " + option.getKey());
+ this.option = option;
+ }
+
+ /**
+ * Return the option requiring an argument that wasn't provided
+ * on the command line.
+ *
+ * @return the related option
+ * @since 1.2
+ */
+ public Option getOption()
+ {
+ return option;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/MissingOptionException.java b/src/main/java/org/apache/commons/cli/MissingOptionException.java
new file mode 100644
index 00000000..b7df24e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/MissingOptionException.java
@@ -0,0 +1,96 @@
+/**
+ * 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.commons.cli;
+
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * Thrown when a required option has not been provided.
+ *
+ * @version $Id: MissingOptionException.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+public class MissingOptionException extends ParseException
+{
+ /** This exception {@code serialVersionUID}. */
+ private static final long serialVersionUID = 8161889051578563249L;
+
+ /** The list of missing options and groups */
+ private List missingOptions;
+
+ /**
+ * Construct a new MissingSelectedException
+ * with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public MissingOptionException(String message)
+ {
+ super(message);
+ }
+
+ /**
+ * Constructs a new MissingSelectedException
with the
+ * specified list of missing options.
+ *
+ * @param missingOptions the list of missing options and groups
+ * @since 1.2
+ */
+ public MissingOptionException(List missingOptions)
+ {
+ this(createMessage(missingOptions));
+ this.missingOptions = missingOptions;
+ }
+
+ /**
+ * Returns the list of options or option groups missing in the command line parsed.
+ *
+ * @return the missing options, consisting of String instances for simple
+ * options, and OptionGroup instances for required option groups.
+ * @since 1.2
+ */
+ public List getMissingOptions()
+ {
+ return missingOptions;
+ }
+
+ /**
+ * Build the exception message from the specified list of options.
+ *
+ * @param missingOptions the list of missing options and groups
+ * @since 1.2
+ */
+ private static String createMessage(List> missingOptions)
+ {
+ StringBuilder buf = new StringBuilder("Missing required option");
+ buf.append(missingOptions.size() == 1 ? "" : "s");
+ buf.append(": ");
+
+ Iterator> it = missingOptions.iterator();
+ while (it.hasNext())
+ {
+ buf.append(it.next());
+ if (it.hasNext())
+ {
+ buf.append(", ");
+ }
+ }
+
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/Option.java b/src/main/java/org/apache/commons/cli/Option.java
new file mode 100644
index 00000000..2e67e4af
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/Option.java
@@ -0,0 +1,1006 @@
+/**
+ * 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.commons.cli;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes a single command-line option. It maintains
+ * information regarding the short-name of the option, the long-name,
+ * if any exists, a flag indicating if an argument is required for
+ * this option, and a self-documenting description of the option.
+ *
+ * An Option is not created independently, but is created through
+ * an instance of {@link Options}. An Option is required to have
+ * at least a short or a long-name.
+ *
+ * Note: once an {@link Option} has been added to an instance
+ * of {@link Options}, it's required flag may not be changed anymore.
+ *
+ * @see org.apache.commons.cli.Options
+ * @see org.apache.commons.cli.CommandLine
+ *
+ * @version $Id: Option.java 1677406 2015-05-03 14:27:31Z britter $
+ */
+public class Option implements Cloneable, Serializable
+{
+ /** constant that specifies the number of argument values has not been specified */
+ public static final int UNINITIALIZED = -1;
+
+ /** constant that specifies the number of argument values is infinite */
+ public static final int UNLIMITED_VALUES = -2;
+
+ /** The serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** the name of the option */
+ private final String opt;
+
+ /** the long representation of the option */
+ private String longOpt;
+
+ /** the name of the argument for this option */
+ private String argName;
+
+ /** description of the option */
+ private String description;
+
+ /** specifies whether this option is required to be present */
+ private boolean required;
+
+ /** specifies whether the argument value of this Option is optional */
+ private boolean optionalArg;
+
+ /** the number of argument values this option can have */
+ private int numberOfArgs = UNINITIALIZED;
+
+ /** the type of this Option */
+ private Class> type = String.class;
+
+ /** the list of argument values **/
+ private List values = new ArrayList();
+
+ /** the character that is the value separator */
+ private char valuesep;
+
+ /**
+ * Private constructor used by the nested Builder class.
+ *
+ * @param builder builder used to create this option
+ */
+ private Option(final Builder builder)
+ {
+ this.argName = builder.argName;
+ this.description = builder.description;
+ this.longOpt = builder.longOpt;
+ this.numberOfArgs = builder.numberOfArgs;
+ this.opt = builder.opt;
+ this.optionalArg = builder.optionalArg;
+ this.required = builder.required;
+ this.type = builder.type;
+ this.valuesep = builder.valuesep;
+ }
+
+ /**
+ * Creates an Option using the specified parameters.
+ * The option does not take an argument.
+ *
+ * @param opt short representation of the option
+ * @param description describes the function of the option
+ *
+ * @throws IllegalArgumentException if there are any non valid
+ * Option characters in opt
.
+ */
+ public Option(String opt, String description) throws IllegalArgumentException
+ {
+ this(opt, null, false, description);
+ }
+
+ /**
+ * Creates an Option using the specified parameters.
+ *
+ * @param opt short representation of the option
+ * @param hasArg specifies whether the Option takes an argument or not
+ * @param description describes the function of the option
+ *
+ * @throws IllegalArgumentException if there are any non valid
+ * Option characters in opt
.
+ */
+ public Option(String opt, boolean hasArg, String description) throws IllegalArgumentException
+ {
+ this(opt, null, hasArg, description);
+ }
+
+ /**
+ * Creates an Option using the specified parameters.
+ *
+ * @param opt short representation of the option
+ * @param longOpt the long representation of the option
+ * @param hasArg specifies whether the Option takes an argument or not
+ * @param description describes the function of the option
+ *
+ * @throws IllegalArgumentException if there are any non valid
+ * Option characters in opt
.
+ */
+ public Option(String opt, String longOpt, boolean hasArg, String description)
+ throws IllegalArgumentException
+ {
+ // ensure that the option is valid
+ OptionValidator.validateOption(opt);
+
+ this.opt = opt;
+ this.longOpt = longOpt;
+
+ // if hasArg is set then the number of arguments is 1
+ if (hasArg)
+ {
+ this.numberOfArgs = 1;
+ }
+
+ this.description = description;
+ }
+
+ /**
+ * Returns the id of this Option. This is only set when the
+ * Option shortOpt is a single character. This is used for switch
+ * statements.
+ *
+ * @return the id of this Option
+ */
+ public int getId()
+ {
+ return getKey().charAt(0);
+ }
+
+ /**
+ * Returns the 'unique' Option identifier.
+ *
+ * @return the 'unique' Option identifier
+ */
+ String getKey()
+ {
+ // if 'opt' is null, then it is a 'long' option
+ return (opt == null) ? longOpt : opt;
+ }
+
+ /**
+ * Retrieve the name of this Option.
+ *
+ * It is this String which can be used with
+ * {@link CommandLine#hasOption(String opt)} and
+ * {@link CommandLine#getOptionValue(String opt)} to check
+ * for existence and argument.
+ *
+ * @return The name of this option
+ */
+ public String getOpt()
+ {
+ return opt;
+ }
+
+ /**
+ * Retrieve the type of this Option.
+ *
+ * @return The type of this option
+ */
+ public Object getType()
+ {
+ return type;
+ }
+
+ /**
+ * Sets the type of this Option.
+ *
+ * Note: this method is kept for binary compatibility and the
+ * input type is supposed to be a {@link Class} object.
+ *
+ * @param type the type of this Option
+ * @deprecated since 1.3, use {@link #setType(Class)} instead
+ */
+ @Deprecated
+ public void setType(Object type)
+ {
+ setType((Class>) type);
+ }
+
+ /**
+ * Sets the type of this Option.
+ *
+ * @param type the type of this Option
+ * @since 1.3
+ */
+ public void setType(Class> type)
+ {
+ this.type = type;
+ }
+
+ /**
+ * Retrieve the long name of this Option.
+ *
+ * @return Long name of this option, or null, if there is no long name
+ */
+ public String getLongOpt()
+ {
+ return longOpt;
+ }
+
+ /**
+ * Sets the long name of this Option.
+ *
+ * @param longOpt the long name of this Option
+ */
+ public void setLongOpt(String longOpt)
+ {
+ this.longOpt = longOpt;
+ }
+
+ /**
+ * Sets whether this Option can have an optional argument.
+ *
+ * @param optionalArg specifies whether the Option can have
+ * an optional argument.
+ */
+ public void setOptionalArg(boolean optionalArg)
+ {
+ this.optionalArg = optionalArg;
+ }
+
+ /**
+ * @return whether this Option can have an optional argument
+ */
+ public boolean hasOptionalArg()
+ {
+ return optionalArg;
+ }
+
+ /**
+ * Query to see if this Option has a long name
+ *
+ * @return boolean flag indicating existence of a long name
+ */
+ public boolean hasLongOpt()
+ {
+ return longOpt != null;
+ }
+
+ /**
+ * Query to see if this Option requires an argument
+ *
+ * @return boolean flag indicating if an argument is required
+ */
+ public boolean hasArg()
+ {
+ return numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES;
+ }
+
+ /**
+ * Retrieve the self-documenting description of this Option
+ *
+ * @return The string description of this option
+ */
+ public String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ * Sets the self-documenting description of this Option
+ *
+ * @param description The description of this option
+ * @since 1.1
+ */
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+ /**
+ * Query to see if this Option is mandatory
+ *
+ * @return boolean flag indicating whether this Option is mandatory
+ */
+ public boolean isRequired()
+ {
+ return required;
+ }
+
+ /**
+ * Sets whether this Option is mandatory.
+ *
+ * @param required specifies whether this Option is mandatory
+ */
+ public void setRequired(boolean required)
+ {
+ this.required = required;
+ }
+
+ /**
+ * Sets the display name for the argument value.
+ *
+ * @param argName the display name for the argument value.
+ */
+ public void setArgName(String argName)
+ {
+ this.argName = argName;
+ }
+
+ /**
+ * Gets the display name for the argument value.
+ *
+ * @return the display name for the argument value.
+ */
+ public String getArgName()
+ {
+ return argName;
+ }
+
+ /**
+ * Returns whether the display name for the argument value has been set.
+ *
+ * @return if the display name for the argument value has been set.
+ */
+ public boolean hasArgName()
+ {
+ return argName != null && argName.length() > 0;
+ }
+
+ /**
+ * Query to see if this Option can take many values.
+ *
+ * @return boolean flag indicating if multiple values are allowed
+ */
+ public boolean hasArgs()
+ {
+ return numberOfArgs > 1 || numberOfArgs == UNLIMITED_VALUES;
+ }
+
+ /**
+ * Sets the number of argument values this Option can take.
+ *
+ * @param num the number of argument values
+ */
+ public void setArgs(int num)
+ {
+ this.numberOfArgs = num;
+ }
+
+ /**
+ * Sets the value separator. For example if the argument value
+ * was a Java property, the value separator would be '='.
+ *
+ * @param sep The value separator.
+ */
+ public void setValueSeparator(char sep)
+ {
+ this.valuesep = sep;
+ }
+
+ /**
+ * Returns the value separator character.
+ *
+ * @return the value separator character.
+ */
+ public char getValueSeparator()
+ {
+ return valuesep;
+ }
+
+ /**
+ * Return whether this Option has specified a value separator.
+ *
+ * @return whether this Option has specified a value separator.
+ * @since 1.1
+ */
+ public boolean hasValueSeparator()
+ {
+ return valuesep > 0;
+ }
+
+ /**
+ * Returns the number of argument values this Option can take.
+ *
+ * @return num the number of argument values
+ */
+ public int getArgs()
+ {
+ return numberOfArgs;
+ }
+
+ /**
+ * Adds the specified value to this Option.
+ *
+ * @param value is a/the value of this Option
+ */
+ void addValueForProcessing(String value)
+ {
+ if (numberOfArgs == UNINITIALIZED)
+ {
+ throw new RuntimeException("NO_ARGS_ALLOWED");
+ }
+ processValue(value);
+ }
+
+ /**
+ * Processes the value. If this Option has a value separator
+ * the value will have to be parsed into individual tokens. When
+ * n-1 tokens have been processed and there are more value separators
+ * in the value, parsing is ceased and the remaining characters are
+ * added as a single token.
+ *
+ * @param value The String to be processed.
+ *
+ * @since 1.0.1
+ */
+ private void processValue(String value)
+ {
+ // this Option has a separator character
+ if (hasValueSeparator())
+ {
+ // get the separator character
+ char sep = getValueSeparator();
+
+ // store the index for the value separator
+ int index = value.indexOf(sep);
+
+ // while there are more value separators
+ while (index != -1)
+ {
+ // next value to be added
+ if (values.size() == numberOfArgs - 1)
+ {
+ break;
+ }
+
+ // store
+ add(value.substring(0, index));
+
+ // parse
+ value = value.substring(index + 1);
+
+ // get new index
+ index = value.indexOf(sep);
+ }
+ }
+
+ // store the actual value or the last value that has been parsed
+ add(value);
+ }
+
+ /**
+ * Add the value to this Option. If the number of arguments
+ * is greater than zero and there is enough space in the list then
+ * add the value. Otherwise, throw a runtime exception.
+ *
+ * @param value The value to be added to this Option
+ *
+ * @since 1.0.1
+ */
+ private void add(String value)
+ {
+ if (!acceptsArg())
+ {
+ throw new RuntimeException("Cannot add value, list full.");
+ }
+
+ // store value
+ values.add(value);
+ }
+
+ /**
+ * Returns the specified value of this Option or
+ * null
if there is no value.
+ *
+ * @return the value/first value of this Option or
+ * null
if there is no value.
+ */
+ public String getValue()
+ {
+ return hasNoValues() ? null : values.get(0);
+ }
+
+ /**
+ * Returns the specified value of this Option or
+ * null
if there is no value.
+ *
+ * @param index The index of the value to be returned.
+ *
+ * @return the specified value of this Option or
+ * null
if there is no value.
+ *
+ * @throws IndexOutOfBoundsException if index is less than 1
+ * or greater than the number of the values for this Option.
+ */
+ public String getValue(int index) throws IndexOutOfBoundsException
+ {
+ return hasNoValues() ? null : values.get(index);
+ }
+
+ /**
+ * Returns the value/first value of this Option or the
+ * defaultValue
if there is no value.
+ *
+ * @param defaultValue The value to be returned if there
+ * is no value.
+ *
+ * @return the value/first value of this Option or the
+ * defaultValue
if there are no values.
+ */
+ public String getValue(String defaultValue)
+ {
+ String value = getValue();
+
+ return (value != null) ? value : defaultValue;
+ }
+
+ /**
+ * Return the values of this Option as a String array
+ * or null if there are no values
+ *
+ * @return the values of this Option as a String array
+ * or null if there are no values
+ */
+ public String[] getValues()
+ {
+ return hasNoValues() ? null : values.toArray(new String[values.size()]);
+ }
+
+ /**
+ * @return the values of this Option as a List
+ * or null if there are no values
+ */
+ public List getValuesList()
+ {
+ return values;
+ }
+
+ /**
+ * Dump state, suitable for debugging.
+ *
+ * @return Stringified form of this object
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder().append("[ option: ");
+
+ buf.append(opt);
+
+ if (longOpt != null)
+ {
+ buf.append(" ").append(longOpt);
+ }
+
+ buf.append(" ");
+
+ if (hasArgs())
+ {
+ buf.append("[ARG...]");
+ }
+ else if (hasArg())
+ {
+ buf.append(" [ARG]");
+ }
+
+ buf.append(" :: ").append(description);
+
+ if (type != null)
+ {
+ buf.append(" :: ").append(type);
+ }
+
+ buf.append(" ]");
+
+ return buf.toString();
+ }
+
+ /**
+ * Returns whether this Option has any values.
+ *
+ * @return whether this Option has any values.
+ */
+ private boolean hasNoValues()
+ {
+ return values.isEmpty();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+
+ Option option = (Option) o;
+
+
+ if (opt != null ? !opt.equals(option.opt) : option.opt != null)
+ {
+ return false;
+ }
+ if (longOpt != null ? !longOpt.equals(option.longOpt) : option.longOpt != null)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result;
+ result = opt != null ? opt.hashCode() : 0;
+ result = 31 * result + (longOpt != null ? longOpt.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * A rather odd clone method - due to incorrect code in 1.0 it is public
+ * and in 1.1 rather than throwing a CloneNotSupportedException it throws
+ * a RuntimeException so as to maintain backwards compat at the API level.
+ *
+ * After calling this method, it is very likely you will want to call
+ * clearValues().
+ *
+ * @return a clone of this Option instance
+ * @throws RuntimeException if a {@link CloneNotSupportedException} has been thrown
+ * by {@code super.clone()}
+ */
+ @Override
+ public Object clone()
+ {
+ try
+ {
+ Option option = (Option) super.clone();
+ option.values = new ArrayList(values);
+ return option;
+ }
+ catch (CloneNotSupportedException cnse)
+ {
+ throw new RuntimeException("A CloneNotSupportedException was thrown: " + cnse.getMessage());
+ }
+ }
+
+ /**
+ * Clear the Option values. After a parse is complete, these are left with
+ * data in them and they need clearing if another parse is done.
+ *
+ * See: CLI-71
+ */
+ void clearValues()
+ {
+ values.clear();
+ }
+
+ /**
+ * This method is not intended to be used. It was a piece of internal
+ * API that was made public in 1.0. It currently throws an UnsupportedOperationException.
+ *
+ * @param value the value to add
+ * @return always throws an {@link UnsupportedOperationException}
+ * @throws UnsupportedOperationException always
+ * @deprecated
+ */
+ @Deprecated
+ public boolean addValue(String value)
+ {
+ throw new UnsupportedOperationException("The addValue method is not intended for client use. "
+ + "Subclasses should use the addValueForProcessing method instead. ");
+ }
+
+ /**
+ * Tells if the option can accept more arguments.
+ *
+ * @return false if the maximum number of arguments is reached
+ * @since 1.3
+ */
+ boolean acceptsArg()
+ {
+ return (hasArg() || hasArgs() || hasOptionalArg()) && (numberOfArgs <= 0 || values.size() < numberOfArgs);
+ }
+
+ /**
+ * Tells if the option requires more arguments to be valid.
+ *
+ * @return false if the option doesn't require more arguments
+ * @since 1.3
+ */
+ boolean requiresArg()
+ {
+ if (optionalArg)
+ {
+ return false;
+ }
+ if (numberOfArgs == UNLIMITED_VALUES)
+ {
+ return values.isEmpty();
+ }
+ return acceptsArg();
+ }
+
+ /**
+ * Returns a {@link Builder} to create an {@link Option} using descriptive
+ * methods.
+ *
+ * @return a new {@link Builder} instance
+ * @since 1.3
+ */
+ public static Builder builder()
+ {
+ return builder(null);
+ }
+
+ /**
+ * Returns a {@link Builder} to create an {@link Option} using descriptive
+ * methods.
+ *
+ * @param opt short representation of the option
+ * @return a new {@link Builder} instance
+ * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt}
+ * @since 1.3
+ */
+ public static Builder builder(final String opt)
+ {
+ return new Builder(opt);
+ }
+
+ /**
+ * A nested builder class to create Option
instances
+ * using descriptive methods.
+ *
+ * Example usage:
+ *
+ * Option option = Option.builder("a")
+ * .required(true)
+ * .longOpt("arg-name")
+ * .build();
+ *
+ *
+ * @since 1.3
+ */
+ public static final class Builder
+ {
+ /** the name of the option */
+ private final String opt;
+
+ /** description of the option */
+ private String description;
+
+ /** the long representation of the option */
+ private String longOpt;
+
+ /** the name of the argument for this option */
+ private String argName;
+
+ /** specifies whether this option is required to be present */
+ private boolean required;
+
+ /** specifies whether the argument value of this Option is optional */
+ private boolean optionalArg;
+
+ /** the number of argument values this option can have */
+ private int numberOfArgs = UNINITIALIZED;
+
+ /** the type of this Option */
+ private Class> type = String.class;
+
+ /** the character that is the value separator */
+ private char valuesep;
+
+ /**
+ * Constructs a new Builder
with the minimum
+ * required parameters for an Option
instance.
+ *
+ * @param opt short representation of the option
+ * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt}
+ */
+ private Builder(final String opt) throws IllegalArgumentException
+ {
+ OptionValidator.validateOption(opt);
+ this.opt = opt;
+ }
+
+ /**
+ * Sets the display name for the argument value.
+ *
+ * @param argName the display name for the argument value.
+ * @return this builder, to allow method chaining
+ */
+ public Builder argName(final String argName)
+ {
+ this.argName = argName;
+ return this;
+ }
+
+ /**
+ * Sets the description for this option.
+ *
+ * @param description the description of the option.
+ * @return this builder, to allow method chaining
+ */
+ public Builder desc(final String description)
+ {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Sets the long name of the Option.
+ *
+ * @param longOpt the long name of the Option
+ * @return this builder, to allow method chaining
+ */
+ public Builder longOpt(final String longOpt)
+ {
+ this.longOpt = longOpt;
+ return this;
+ }
+
+ /**
+ * Sets the number of argument values the Option can take.
+ *
+ * @param numberOfArgs the number of argument values
+ * @return this builder, to allow method chaining
+ */
+ public Builder numberOfArgs(final int numberOfArgs)
+ {
+ this.numberOfArgs = numberOfArgs;
+ return this;
+ }
+
+ /**
+ * Sets whether the Option can have an optional argument.
+ *
+ * @param isOptional specifies whether the Option can have
+ * an optional argument.
+ * @return this builder, to allow method chaining
+ */
+ public Builder optionalArg(final boolean isOptional)
+ {
+ this.optionalArg = isOptional;
+ return this;
+ }
+
+ /**
+ * Marks this Option as required.
+ *
+ * @return this builder, to allow method chaining
+ */
+ public Builder required()
+ {
+ return required(true);
+ }
+
+ /**
+ * Sets whether the Option is mandatory.
+ *
+ * @param required specifies whether the Option is mandatory
+ * @return this builder, to allow method chaining
+ */
+ public Builder required(final boolean required)
+ {
+ this.required = required;
+ return this;
+ }
+
+ /**
+ * Sets the type of the Option.
+ *
+ * @param type the type of the Option
+ * @return this builder, to allow method chaining
+ */
+ public Builder type(final Class> type)
+ {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * The Option will use '=' as a means to separate argument value.
+ *
+ * @return this builder, to allow method chaining
+ */
+ public Builder valueSeparator()
+ {
+ return valueSeparator('=');
+ }
+
+ /**
+ * The Option will use sep
as a means to
+ * separate argument values.
+ *
+ * Example:
+ *
+ * Option opt = Option.builder("D").hasArgs()
+ * .valueSeparator('=')
+ * .build();
+ * Options options = new Options();
+ * options.addOption(opt);
+ * String[] args = {"-Dkey=value"};
+ * CommandLineParser parser = new DefaultParser();
+ * CommandLine line = parser.parse(options, args);
+ * String propertyName = line.getOptionValues("D")[0]; // will be "key"
+ * String propertyValue = line.getOptionValues("D")[1]; // will be "value"
+ *
+ *
+ * @param sep The value separator.
+ * @return this builder, to allow method chaining
+ */
+ public Builder valueSeparator(final char sep)
+ {
+ valuesep = sep;
+ return this;
+ }
+
+ /**
+ * Indicates that the Option will require an argument.
+ *
+ * @return this builder, to allow method chaining
+ */
+ public Builder hasArg()
+ {
+ return hasArg(true);
+ }
+
+ /**
+ * Indicates if the Option has an argument or not.
+ *
+ * @param hasArg specifies whether the Option takes an argument or not
+ * @return this builder, to allow method chaining
+ */
+ public Builder hasArg(final boolean hasArg)
+ {
+ // set to UNINITIALIZED when no arg is specified to be compatible with OptionBuilder
+ numberOfArgs = hasArg ? 1 : Option.UNINITIALIZED;
+ return this;
+ }
+
+ /**
+ * Indicates that the Option can have unlimited argument values.
+ *
+ * @return this builder, to allow method chaining
+ */
+ public Builder hasArgs()
+ {
+ numberOfArgs = Option.UNLIMITED_VALUES;
+ return this;
+ }
+
+ /**
+ * Constructs an Option with the values declared by this {@link Builder}.
+ *
+ * @return the new {@link Option}
+ * @throws IllegalArgumentException if neither {@code opt} or {@code longOpt} has been set
+ */
+ public Option build()
+ {
+ if (opt == null && longOpt == null)
+ {
+ throw new IllegalArgumentException("Either opt or longOpt must be specified");
+ }
+ return new Option(this);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/OptionBuilder.java b/src/main/java/org/apache/commons/cli/OptionBuilder.java
new file mode 100644
index 00000000..6b287d3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/OptionBuilder.java
@@ -0,0 +1,396 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * OptionBuilder allows the user to create Options using descriptive methods.
+ *
+ * Details on the Builder pattern can be found at
+ * http://c2.com/cgi-bin/wiki?BuilderPattern .
+ *
+ * This class is NOT thread safe. See CLI-209
+ *
+ * @version $Id: OptionBuilder.java 1677400 2015-05-03 13:46:08Z britter $
+ * @since 1.0
+ * @deprecated since 1.3, use {@link Option#builder(String)} instead
+ */
+@Deprecated
+public final class OptionBuilder
+{
+ /** long option */
+ private static String longopt;
+
+ /** option description */
+ private static String description;
+
+ /** argument name */
+ private static String argName;
+
+ /** is required? */
+ private static boolean required;
+
+ /** the number of arguments */
+ private static int numberOfArgs = Option.UNINITIALIZED;
+
+ /** option type */
+ private static Class> type;
+
+ /** option can have an optional argument value */
+ private static boolean optionalArg;
+
+ /** value separator for argument value */
+ private static char valuesep;
+
+ /** option builder instance */
+ private static final OptionBuilder INSTANCE = new OptionBuilder();
+
+ static
+ {
+ // ensure the consistency of the initial values
+ reset();
+ }
+
+ /**
+ * private constructor to prevent instances being created
+ */
+ private OptionBuilder()
+ {
+ // hide the constructor
+ }
+
+ /**
+ * Resets the member variables to their default values.
+ */
+ private static void reset()
+ {
+ description = null;
+ argName = null;
+ longopt = null;
+ type = String.class;
+ required = false;
+ numberOfArgs = Option.UNINITIALIZED;
+ optionalArg = false;
+ valuesep = (char) 0;
+ }
+
+ /**
+ * The next Option created will have the following long option value.
+ *
+ * @param newLongopt the long option value
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder withLongOpt(String newLongopt)
+ {
+ OptionBuilder.longopt = newLongopt;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will require an argument value.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasArg()
+ {
+ OptionBuilder.numberOfArgs = 1;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will require an argument value if
+ * hasArg
is true.
+ *
+ * @param hasArg if true then the Option has an argument value
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasArg(boolean hasArg)
+ {
+ OptionBuilder.numberOfArgs = hasArg ? 1 : Option.UNINITIALIZED;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will have the specified argument value name.
+ *
+ * @param name the name for the argument value
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder withArgName(String name)
+ {
+ OptionBuilder.argName = name;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will be required.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder isRequired()
+ {
+ OptionBuilder.required = true;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created uses sep
as a means to
+ * separate argument values.
+ *
+ * Example:
+ *
+ * Option opt = OptionBuilder.withValueSeparator('=')
+ * .create('D');
+ *
+ * String args = "-Dkey=value";
+ * CommandLine line = parser.parse(args);
+ * String propertyName = opt.getValue(0); // will be "key"
+ * String propertyValue = opt.getValue(1); // will be "value"
+ *
+ *
+ * @param sep The value separator to be used for the argument values.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder withValueSeparator(char sep)
+ {
+ OptionBuilder.valuesep = sep;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created uses '=
' as a means to
+ * separate argument values.
+ *
+ * Example:
+ *
+ * Option opt = OptionBuilder.withValueSeparator()
+ * .create('D');
+ *
+ * CommandLine line = parser.parse(args);
+ * String propertyName = opt.getValue(0);
+ * String propertyValue = opt.getValue(1);
+ *
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder withValueSeparator()
+ {
+ OptionBuilder.valuesep = '=';
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will be required if required
+ * is true.
+ *
+ * @param newRequired if true then the Option is required
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder isRequired(boolean newRequired)
+ {
+ OptionBuilder.required = newRequired;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created can have unlimited argument values.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasArgs()
+ {
+ OptionBuilder.numberOfArgs = Option.UNLIMITED_VALUES;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created can have num
argument values.
+ *
+ * @param num the number of args that the option can have
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasArgs(int num)
+ {
+ OptionBuilder.numberOfArgs = num;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option can have an optional argument.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasOptionalArg()
+ {
+ OptionBuilder.numberOfArgs = 1;
+ OptionBuilder.optionalArg = true;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option can have an unlimited number of optional arguments.
+ *
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasOptionalArgs()
+ {
+ OptionBuilder.numberOfArgs = Option.UNLIMITED_VALUES;
+ OptionBuilder.optionalArg = true;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option can have the specified number of optional arguments.
+ *
+ * @param numArgs - the maximum number of optional arguments
+ * the next Option created can have.
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder hasOptionalArgs(int numArgs)
+ {
+ OptionBuilder.numberOfArgs = numArgs;
+ OptionBuilder.optionalArg = true;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will have a value that will be an instance
+ * of type
.
+ *
+ * Note: this method is kept for binary compatibility and the
+ * input type is supposed to be a {@link Class} object.
+ *
+ * @param newType the type of the Options argument value
+ * @return the OptionBuilder instance
+ * @deprecated since 1.3, use {@link #withType(Class)} instead
+ */
+ @Deprecated
+ public static OptionBuilder withType(Object newType)
+ {
+ return withType((Class>) newType);
+ }
+
+ /**
+ * The next Option created will have a value that will be an instance
+ * of type
.
+ *
+ * @param newType the type of the Options argument value
+ * @return the OptionBuilder instance
+ * @since 1.3
+ */
+ public static OptionBuilder withType(Class> newType)
+ {
+ OptionBuilder.type = newType;
+
+ return INSTANCE;
+ }
+
+ /**
+ * The next Option created will have the specified description
+ *
+ * @param newDescription a description of the Option's purpose
+ * @return the OptionBuilder instance
+ */
+ public static OptionBuilder withDescription(String newDescription)
+ {
+ OptionBuilder.description = newDescription;
+
+ return INSTANCE;
+ }
+
+ /**
+ * Create an Option using the current settings and with
+ * the specified Option char
.
+ *
+ * @param opt the character representation of the Option
+ * @return the Option instance
+ * @throws IllegalArgumentException if opt
is not
+ * a valid character. See Option.
+ */
+ public static Option create(char opt) throws IllegalArgumentException
+ {
+ return create(String.valueOf(opt));
+ }
+
+ /**
+ * Create an Option using the current settings
+ *
+ * @return the Option instance
+ * @throws IllegalArgumentException if longOpt
has not been set.
+ */
+ public static Option create() throws IllegalArgumentException
+ {
+ if (longopt == null)
+ {
+ OptionBuilder.reset();
+ throw new IllegalArgumentException("must specify longopt");
+ }
+
+ return create(null);
+ }
+
+ /**
+ * Create an Option using the current settings and with
+ * the specified Option char
.
+ *
+ * @param opt the java.lang.String
representation
+ * of the Option
+ * @return the Option instance
+ * @throws IllegalArgumentException if opt
is not
+ * a valid character. See Option.
+ */
+ public static Option create(String opt) throws IllegalArgumentException
+ {
+ Option option = null;
+ try
+ {
+ // create the option
+ option = new Option(opt, description);
+
+ // set the option properties
+ option.setLongOpt(longopt);
+ option.setRequired(required);
+ option.setOptionalArg(optionalArg);
+ option.setArgs(numberOfArgs);
+ option.setType(type);
+ option.setValueSeparator(valuesep);
+ option.setArgName(argName);
+ }
+ finally
+ {
+ // reset the OptionBuilder properties
+ OptionBuilder.reset();
+ }
+
+ // return the Option instance
+ return option;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/OptionGroup.java b/src/main/java/org/apache/commons/cli/OptionGroup.java
new file mode 100644
index 00000000..640504cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/OptionGroup.java
@@ -0,0 +1,179 @@
+/**
+ * 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.commons.cli;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A group of mutually exclusive options.
+ *
+ * @version $Id: OptionGroup.java 1669814 2015-03-28 18:09:26Z britter $
+ */
+public class OptionGroup implements Serializable
+{
+ /** The serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** hold the options */
+ private final Map optionMap = new HashMap();
+
+ /** the name of the selected option */
+ private String selected;
+
+ /** specified whether this group is required */
+ private boolean required;
+
+ /**
+ * Add the specified Option
to this group.
+ *
+ * @param option the option to add to this group
+ * @return this option group with the option added
+ */
+ public OptionGroup addOption(Option option)
+ {
+ // key - option name
+ // value - the option
+ optionMap.put(option.getKey(), option);
+
+ return this;
+ }
+
+ /**
+ * @return the names of the options in this group as a
+ * Collection
+ */
+ public Collection getNames()
+ {
+ // the key set is the collection of names
+ return optionMap.keySet();
+ }
+
+ /**
+ * @return the options in this group as a Collection
+ */
+ public Collection getOptions()
+ {
+ // the values are the collection of options
+ return optionMap.values();
+ }
+
+ /**
+ * Set the selected option of this group to name
.
+ *
+ * @param option the option that is selected
+ * @throws AlreadySelectedException if an option from this group has
+ * already been selected.
+ */
+ public void setSelected(Option option) throws AlreadySelectedException
+ {
+ if (option == null)
+ {
+ // reset the option previously selected
+ selected = null;
+ return;
+ }
+
+ // if no option has already been selected or the
+ // same option is being reselected then set the
+ // selected member variable
+ if (selected == null || selected.equals(option.getKey()))
+ {
+ selected = option.getKey();
+ }
+ else
+ {
+ throw new AlreadySelectedException(this, option);
+ }
+ }
+
+ /**
+ * @return the selected option name
+ */
+ public String getSelected()
+ {
+ return selected;
+ }
+
+ /**
+ * @param required specifies if this group is required
+ */
+ public void setRequired(boolean required)
+ {
+ this.required = required;
+ }
+
+ /**
+ * Returns whether this option group is required.
+ *
+ * @return whether this option group is required
+ */
+ public boolean isRequired()
+ {
+ return required;
+ }
+
+ /**
+ * Returns the stringified version of this OptionGroup.
+ *
+ * @return the stringified representation of this group
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buff = new StringBuilder();
+
+ Iterator iter = getOptions().iterator();
+
+ buff.append("[");
+
+ while (iter.hasNext())
+ {
+ Option option = iter.next();
+
+ if (option.getOpt() != null)
+ {
+ buff.append("-");
+ buff.append(option.getOpt());
+ }
+ else
+ {
+ buff.append("--");
+ buff.append(option.getLongOpt());
+ }
+
+ if (option.getDescription() != null)
+ {
+ buff.append(" ");
+ buff.append(option.getDescription());
+ }
+
+ if (iter.hasNext())
+ {
+ buff.append(", ");
+ }
+ }
+
+ buff.append("]");
+
+ return buff.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/OptionValidator.java b/src/main/java/org/apache/commons/cli/OptionValidator.java
new file mode 100644
index 00000000..659ee43c
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/OptionValidator.java
@@ -0,0 +1,99 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * Validates an Option string.
+ *
+ * @version $Id: OptionValidator.java 1544819 2013-11-23 15:34:31Z tn $
+ * @since 1.1
+ */
+final class OptionValidator
+{
+ /**
+ * Validates whether opt
is a permissible Option
+ * shortOpt. The rules that specify if the opt
+ * is valid are:
+ *
+ *
+ * a single character opt
that is either
+ * ' '(special case), '?', '@' or a letter
+ * a multi character opt
that only contains
+ * letters.
+ *
+ *
+ * In case {@code opt} is {@code null} no further validation is performed.
+ *
+ * @param opt The option string to validate, may be null
+ * @throws IllegalArgumentException if the Option is not valid.
+ */
+ static void validateOption(String opt) throws IllegalArgumentException
+ {
+ // if opt is NULL do not check further
+ if (opt == null)
+ {
+ return;
+ }
+
+ // handle the single character opt
+ if (opt.length() == 1)
+ {
+ char ch = opt.charAt(0);
+
+ if (!isValidOpt(ch))
+ {
+ throw new IllegalArgumentException("Illegal option name '" + ch + "'");
+ }
+ }
+
+ // handle the multi character opt
+ else
+ {
+ for (char ch : opt.toCharArray())
+ {
+ if (!isValidChar(ch))
+ {
+ throw new IllegalArgumentException("The option '" + opt + "' contains an illegal "
+ + "character : '" + ch + "'");
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether the specified character is a valid Option.
+ *
+ * @param c the option to validate
+ * @return true if c
is a letter, '?' or '@', otherwise false.
+ */
+ private static boolean isValidOpt(char c)
+ {
+ return isValidChar(c) || c == '?' || c == '@';
+ }
+
+ /**
+ * Returns whether the specified character is a valid character.
+ *
+ * @param c the character to validate
+ * @return true if c
is a letter.
+ */
+ private static boolean isValidChar(char c)
+ {
+ return Character.isJavaIdentifierPart(c);
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/Options.java b/src/main/java/org/apache/commons/cli/Options.java
new file mode 100644
index 00000000..ba0af568
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/Options.java
@@ -0,0 +1,327 @@
+/**
+ * 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.commons.cli;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main entry-point into the library.
+ *
+ * Options represents a collection of {@link Option} objects, which
+ * describe the possible options for a command-line.
+ *
+ * It may flexibly parse long and short options, with or without
+ * values. Additionally, it may parse only a portion of a commandline,
+ * allowing for flexible multi-stage parsing.
+ *
+ * @see org.apache.commons.cli.CommandLine
+ *
+ * @version $Id: Options.java 1685376 2015-06-14 09:51:59Z britter $
+ */
+public class Options implements Serializable
+{
+ /** The serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** a map of the options with the character key */
+ private final Map shortOpts = new LinkedHashMap();
+
+ /** a map of the options with the long key */
+ private final Map longOpts = new LinkedHashMap();
+
+ /** a map of the required options */
+ // N.B. This can contain either a String (addOption) or an OptionGroup (addOptionGroup)
+ // TODO this seems wrong
+ private final List requiredOpts = new ArrayList();
+
+ /** a map of the option groups */
+ private final Map optionGroups = new HashMap();
+
+ /**
+ * Add the specified option group.
+ *
+ * @param group the OptionGroup that is to be added
+ * @return the resulting Options instance
+ */
+ public Options addOptionGroup(OptionGroup group)
+ {
+ if (group.isRequired())
+ {
+ requiredOpts.add(group);
+ }
+
+ for (Option option : group.getOptions())
+ {
+ // an Option cannot be required if it is in an
+ // OptionGroup, either the group is required or
+ // nothing is required
+ option.setRequired(false);
+ addOption(option);
+
+ optionGroups.put(option.getKey(), group);
+ }
+
+ return this;
+ }
+
+ /**
+ * Lists the OptionGroups that are members of this Options instance.
+ *
+ * @return a Collection of OptionGroup instances.
+ */
+ Collection getOptionGroups()
+ {
+ return new HashSet(optionGroups.values());
+ }
+
+ /**
+ * Add an option that only contains a short name.
+ * The option does not take an argument.
+ *
+ * @param opt Short single-character name of the option.
+ * @param description Self-documenting description
+ * @return the resulting Options instance
+ * @since 1.3
+ */
+ public Options addOption(String opt, String description)
+ {
+ addOption(opt, null, false, description);
+ return this;
+ }
+
+ /**
+ * Add an option that only contains a short-name.
+ * It may be specified as requiring an argument.
+ *
+ * @param opt Short single-character name of the option.
+ * @param hasArg flag signally if an argument is required after this option
+ * @param description Self-documenting description
+ * @return the resulting Options instance
+ */
+ public Options addOption(String opt, boolean hasArg, String description)
+ {
+ addOption(opt, null, hasArg, description);
+ return this;
+ }
+
+ /**
+ * Add an option that contains a short-name and a long-name.
+ * It may be specified as requiring an argument.
+ *
+ * @param opt Short single-character name of the option.
+ * @param longOpt Long multi-character name of the option.
+ * @param hasArg flag signally if an argument is required after this option
+ * @param description Self-documenting description
+ * @return the resulting Options instance
+ */
+ public Options addOption(String opt, String longOpt, boolean hasArg, String description)
+ {
+ addOption(new Option(opt, longOpt, hasArg, description));
+ return this;
+ }
+
+ /**
+ * Adds an option instance
+ *
+ * @param opt the option that is to be added
+ * @return the resulting Options instance
+ */
+ public Options addOption(Option opt)
+ {
+ String key = opt.getKey();
+
+ // add it to the long option list
+ if (opt.hasLongOpt())
+ {
+ longOpts.put(opt.getLongOpt(), opt);
+ }
+
+ // if the option is required add it to the required list
+ if (opt.isRequired())
+ {
+ if (requiredOpts.contains(key))
+ {
+ requiredOpts.remove(requiredOpts.indexOf(key));
+ }
+ requiredOpts.add(key);
+ }
+
+ shortOpts.put(key, opt);
+
+ return this;
+ }
+
+ /**
+ * Retrieve a read-only list of options in this set
+ *
+ * @return read-only Collection of {@link Option} objects in this descriptor
+ */
+ public Collection getOptions()
+ {
+ return Collections.unmodifiableCollection(helpOptions());
+ }
+
+ /**
+ * Returns the Options for use by the HelpFormatter.
+ *
+ * @return the List of Options
+ */
+ List helpOptions()
+ {
+ return new ArrayList (shortOpts.values());
+ }
+
+ /**
+ * Returns the required options.
+ *
+ * @return read-only List of required options
+ */
+ public List getRequiredOptions()
+ {
+ return Collections.unmodifiableList(requiredOpts);
+ }
+
+ /**
+ * Retrieve the {@link Option} matching the long or short name specified.
+ * The leading hyphens in the name are ignored (up to 2).
+ *
+ * @param opt short or long name of the {@link Option}
+ * @return the option represented by opt
+ */
+ public Option getOption(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+
+ if (shortOpts.containsKey(opt))
+ {
+ return shortOpts.get(opt);
+ }
+
+ return longOpts.get(opt);
+ }
+
+ /**
+ * Returns the options with a long name starting with the name specified.
+ *
+ * @param opt the partial name of the option
+ * @return the options matching the partial name specified, or an empty list if none matches
+ * @since 1.3
+ */
+ public List getMatchingOptions(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+
+ List matchingOpts = new ArrayList();
+
+ // for a perfect match return the single option only
+ if (longOpts.keySet().contains(opt))
+ {
+ return Collections.singletonList(opt);
+ }
+
+ for (String longOpt : longOpts.keySet())
+ {
+ if (longOpt.startsWith(opt))
+ {
+ matchingOpts.add(longOpt);
+ }
+ }
+
+ return matchingOpts;
+ }
+
+ /**
+ * Returns whether the named {@link Option} is a member of this {@link Options}.
+ *
+ * @param opt short or long name of the {@link Option}
+ * @return true if the named {@link Option} is a member of this {@link Options}
+ */
+ public boolean hasOption(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+
+ return shortOpts.containsKey(opt) || longOpts.containsKey(opt);
+ }
+
+ /**
+ * Returns whether the named {@link Option} is a member of this {@link Options}.
+ *
+ * @param opt long name of the {@link Option}
+ * @return true if the named {@link Option} is a member of this {@link Options}
+ * @since 1.3
+ */
+ public boolean hasLongOption(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+
+ return longOpts.containsKey(opt);
+ }
+
+ /**
+ * Returns whether the named {@link Option} is a member of this {@link Options}.
+ *
+ * @param opt short name of the {@link Option}
+ * @return true if the named {@link Option} is a member of this {@link Options}
+ * @since 1.3
+ */
+ public boolean hasShortOption(String opt)
+ {
+ opt = Util.stripLeadingHyphens(opt);
+
+ return shortOpts.containsKey(opt);
+ }
+
+ /**
+ * Returns the OptionGroup the opt
belongs to.
+ * @param opt the option whose OptionGroup is being queried.
+ *
+ * @return the OptionGroup if opt
is part
+ * of an OptionGroup, otherwise return null
+ */
+ public OptionGroup getOptionGroup(Option opt)
+ {
+ return optionGroups.get(opt.getKey());
+ }
+
+ /**
+ * Dump state, suitable for debugging.
+ *
+ * @return Stringified form of this object
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("[ Options: [ short ");
+ buf.append(shortOpts.toString());
+ buf.append(" ] [ long ");
+ buf.append(longOpts);
+ buf.append(" ]");
+
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/ParseException.java b/src/main/java/org/apache/commons/cli/ParseException.java
new file mode 100644
index 00000000..e0cdd3f0
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/ParseException.java
@@ -0,0 +1,42 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * Base for Exceptions thrown during parsing of a command-line.
+ *
+ * @version $Id: ParseException.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+public class ParseException extends Exception
+{
+ /**
+ * This exception {@code serialVersionUID}.
+ */
+ private static final long serialVersionUID = 9112808380089253192L;
+
+ /**
+ * Construct a new ParseException
+ * with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public ParseException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/Parser.java b/src/main/java/org/apache/commons/cli/Parser.java
new file mode 100644
index 00000000..00252756
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/Parser.java
@@ -0,0 +1,430 @@
+/**
+ * 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.commons.cli;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Properties;
+
+/**
+ * Parser
creates {@link CommandLine}s.
+ *
+ * @version $Id: Parser.java 1677406 2015-05-03 14:27:31Z britter $
+ * @deprecated since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases
+ */
+@Deprecated
+public abstract class Parser implements CommandLineParser
+{
+ /** commandline instance */
+ protected CommandLine cmd;
+
+ /** current Options */
+ private Options options;
+
+ /** list of required options strings */
+ private List requiredOptions;
+
+ protected void setOptions(Options options)
+ {
+ this.options = options;
+ this.requiredOptions = new ArrayList(options.getRequiredOptions());
+ }
+
+ protected Options getOptions()
+ {
+ return options;
+ }
+
+ protected List getRequiredOptions()
+ {
+ return requiredOptions;
+ }
+
+ /**
+ * Subclasses must implement this method to reduce
+ * the arguments
that have been passed to the parse method.
+ *
+ * @param opts The Options to parse the arguments by.
+ * @param arguments The arguments that have to be flattened.
+ * @param stopAtNonOption specifies whether to stop
+ * flattening when a non option has been encountered
+ * @return a String array of the flattened arguments
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption)
+ throws ParseException;
+
+ /**
+ * Parses the specified arguments
based
+ * on the specified {@link Options}.
+ *
+ * @param options the Options
+ * @param arguments the arguments
+ * @return the CommandLine
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ */
+ public CommandLine parse(Options options, String[] arguments) throws ParseException
+ {
+ return parse(options, arguments, null, false);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @return the list of atomic option and value tokens
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ *
+ * @since 1.1
+ */
+ public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
+ {
+ return parse(options, arguments, properties, false);
+ }
+
+ /**
+ * Parses the specified arguments
+ * based on the specified {@link Options}.
+ *
+ * @param options the Options
+ * @param arguments the arguments
+ * @param stopAtNonOption if true an unrecognized argument stops
+ * the parsing and the remaining arguments are added to the
+ * {@link CommandLine}s args list. If false an unrecognized
+ * argument triggers a ParseException.
+ * @return the CommandLine
+ * @throws ParseException if an error occurs when parsing the arguments.
+ */
+ public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
+ {
+ return parse(options, arguments, null, stopAtNonOption);
+ }
+
+ /**
+ * Parse the arguments according to the specified options and
+ * properties.
+ *
+ * @param options the specified Options
+ * @param arguments the command line arguments
+ * @param properties command line option name-value pairs
+ * @param stopAtNonOption if true an unrecognized argument stops
+ * the parsing and the remaining arguments are added to the
+ * {@link CommandLine}s args list. If false an unrecognized
+ * argument triggers a ParseException.
+ *
+ * @return the list of atomic option and value tokens
+ *
+ * @throws ParseException if there are any problems encountered
+ * while parsing the command line tokens.
+ *
+ * @since 1.1
+ */
+ public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
+ throws ParseException
+ {
+ // clear out the data in options in case it's been used before (CLI-71)
+ for (Option opt : options.helpOptions())
+ {
+ opt.clearValues();
+ }
+
+ // clear the data from the groups
+ for (OptionGroup group : options.getOptionGroups())
+ {
+ group.setSelected(null);
+ }
+
+ // initialise members
+ setOptions(options);
+
+ cmd = new CommandLine();
+
+ boolean eatTheRest = false;
+
+ if (arguments == null)
+ {
+ arguments = new String[0];
+ }
+
+ List tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
+
+ ListIterator iterator = tokenList.listIterator();
+
+ // process each flattened token
+ while (iterator.hasNext())
+ {
+ String t = iterator.next();
+
+ // the value is the double-dash
+ if ("--".equals(t))
+ {
+ eatTheRest = true;
+ }
+
+ // the value is a single dash
+ else if ("-".equals(t))
+ {
+ if (stopAtNonOption)
+ {
+ eatTheRest = true;
+ }
+ else
+ {
+ cmd.addArg(t);
+ }
+ }
+
+ // the value is an option
+ else if (t.startsWith("-"))
+ {
+ if (stopAtNonOption && !getOptions().hasOption(t))
+ {
+ eatTheRest = true;
+ cmd.addArg(t);
+ }
+ else
+ {
+ processOption(t, iterator);
+ }
+ }
+
+ // the value is an argument
+ else
+ {
+ cmd.addArg(t);
+
+ if (stopAtNonOption)
+ {
+ eatTheRest = true;
+ }
+ }
+
+ // eat the remaining tokens
+ if (eatTheRest)
+ {
+ while (iterator.hasNext())
+ {
+ String str = iterator.next();
+
+ // ensure only one double-dash is added
+ if (!"--".equals(str))
+ {
+ cmd.addArg(str);
+ }
+ }
+ }
+ }
+
+ processProperties(properties);
+ checkRequiredOptions();
+
+ return cmd;
+ }
+
+ /**
+ * Sets the values of Options using the values in properties
.
+ *
+ * @param properties The value properties to be processed.
+ * @throws ParseException if there are any problems encountered
+ * while processing the properties.
+ */
+ protected void processProperties(Properties properties) throws ParseException
+ {
+ if (properties == null)
+ {
+ return;
+ }
+
+ for (Enumeration> e = properties.propertyNames(); e.hasMoreElements();)
+ {
+ String option = e.nextElement().toString();
+
+ Option opt = options.getOption(option);
+ if (opt == null)
+ {
+ throw new UnrecognizedOptionException("Default option wasn't defined", option);
+ }
+
+ // if the option is part of a group, check if another option of the group has been selected
+ OptionGroup group = options.getOptionGroup(opt);
+ boolean selected = group != null && group.getSelected() != null;
+
+ if (!cmd.hasOption(option) && !selected)
+ {
+ // get the value from the properties instance
+ String value = properties.getProperty(option);
+
+ if (opt.hasArg())
+ {
+ if (opt.getValues() == null || opt.getValues().length == 0)
+ {
+ try
+ {
+ opt.addValueForProcessing(value);
+ }
+ catch (RuntimeException exp) //NOPMD
+ {
+ // if we cannot add the value don't worry about it
+ }
+ }
+ }
+ else if (!("yes".equalsIgnoreCase(value)
+ || "true".equalsIgnoreCase(value)
+ || "1".equalsIgnoreCase(value)))
+ {
+ // if the value is not yes, true or 1 then don't add the
+ // option to the CommandLine
+ continue;
+ }
+
+ cmd.addOption(opt);
+ updateRequiredOptions(opt);
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link MissingOptionException} if all of the required options
+ * are not present.
+ *
+ * @throws MissingOptionException if any of the required Options are not present.
+ */
+ protected void checkRequiredOptions() throws MissingOptionException
+ {
+ // if there are required options that have not been processed
+ if (!getRequiredOptions().isEmpty())
+ {
+ throw new MissingOptionException(getRequiredOptions());
+ }
+ }
+
+ /**
+ * Process the argument values for the specified Option
+ * opt
using the values retrieved from the
+ * specified iterator iter
.
+ *
+ * @param opt The current Option
+ * @param iter The iterator over the flattened command line Options.
+ *
+ * @throws ParseException if an argument value is required
+ * and it is has not been found.
+ */
+ public void processArgs(Option opt, ListIterator iter) throws ParseException
+ {
+ // loop until an option is found
+ while (iter.hasNext())
+ {
+ String str = iter.next();
+
+ // found an Option, not an argument
+ if (getOptions().hasOption(str) && str.startsWith("-"))
+ {
+ iter.previous();
+ break;
+ }
+
+ // found a value
+ try
+ {
+ opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
+ }
+ catch (RuntimeException exp)
+ {
+ iter.previous();
+ break;
+ }
+ }
+
+ if (opt.getValues() == null && !opt.hasOptionalArg())
+ {
+ throw new MissingArgumentException(opt);
+ }
+ }
+
+ /**
+ * Process the Option specified by arg
using the values
+ * retrieved from the specified iterator iter
.
+ *
+ * @param arg The String value representing an Option
+ * @param iter The iterator over the flattened command line arguments.
+ *
+ * @throws ParseException if arg
does not represent an Option
+ */
+ protected void processOption(String arg, ListIterator iter) throws ParseException
+ {
+ boolean hasOption = getOptions().hasOption(arg);
+
+ // if there is no option throw an UnrecognisedOptionException
+ if (!hasOption)
+ {
+ throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
+ }
+
+ // get the option represented by arg
+ Option opt = (Option) getOptions().getOption(arg).clone();
+
+ // update the required options and groups
+ updateRequiredOptions(opt);
+
+ // if the option takes an argument value
+ if (opt.hasArg())
+ {
+ processArgs(opt, iter);
+ }
+
+ // set the option on the command line
+ cmd.addOption(opt);
+ }
+
+ /**
+ * Removes the option or its group from the list of expected elements.
+ *
+ * @param opt
+ */
+ private void updateRequiredOptions(Option opt) throws ParseException
+ {
+ // if the option is a required option remove the option from
+ // the requiredOptions list
+ if (opt.isRequired())
+ {
+ getRequiredOptions().remove(opt.getKey());
+ }
+
+ // if the option is in an OptionGroup make that option the selected
+ // option of the group
+ if (getOptions().getOptionGroup(opt) != null)
+ {
+ OptionGroup group = getOptions().getOptionGroup(opt);
+
+ if (group.isRequired())
+ {
+ getRequiredOptions().remove(group);
+ }
+
+ group.setSelected(opt);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java
new file mode 100644
index 00000000..6a5c4cee
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java
@@ -0,0 +1,207 @@
+/**
+ * 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.commons.cli;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * Allows Options to be created from a single String.
+ * The pattern contains various single character flags and via
+ * an optional punctuation character, their expected type.
+ *
+ *
+ *
+ * Overview of PatternOptionBuilder patterns
+ * a -a flag
+ * b@ -b [classname]
+ * c> -c [filename]
+ * d+ -d [classname] (creates object via empty constructor)
+ * e% -e [number] (creates Double/Long instance depending on existing of a '.')
+ * f/ -f [url]
+ * g: -g [string]
+ *
+ *
+ *
+ * For example, the following allows command line flags of '-v -p string-value -f /dir/file'.
+ * The exclamation mark precede a mandatory option.
+ *
+ *
+ *
+ * Options options = PatternOptionBuilder.parsePattern("vp:!f/");
+ *
+ *
+ *
+ * TODO: These need to break out to OptionType and also to be pluggable.
+ *
+ *
+ * @version $Id: PatternOptionBuilder.java 1677406 2015-05-03 14:27:31Z britter $
+ */
+public class PatternOptionBuilder
+{
+ /** String class */
+ public static final Class STRING_VALUE = String.class;
+
+ /** Object class */
+ public static final Class OBJECT_VALUE = Object.class;
+
+ /** Number class */
+ public static final Class NUMBER_VALUE = Number.class;
+
+ /** Date class */
+ public static final Class DATE_VALUE = Date.class;
+
+ /** Class class */
+ public static final Class> CLASS_VALUE = Class.class;
+
+ /// can we do this one??
+ // is meant to check that the file exists, else it errors.
+ // ie) it's for reading not writing.
+
+ /** FileInputStream class */
+ public static final Class EXISTING_FILE_VALUE = FileInputStream.class;
+
+ /** File class */
+ public static final Class FILE_VALUE = File.class;
+
+ /** File array class */
+ public static final Class FILES_VALUE = File[].class;
+
+ /** URL class */
+ public static final Class URL_VALUE = URL.class;
+
+ /**
+ * Retrieve the class that ch
represents.
+ *
+ * @param ch the specified character
+ * @return The class that ch
represents
+ */
+ public static Object getValueClass(char ch)
+ {
+ switch (ch)
+ {
+ case '@':
+ return PatternOptionBuilder.OBJECT_VALUE;
+ case ':':
+ return PatternOptionBuilder.STRING_VALUE;
+ case '%':
+ return PatternOptionBuilder.NUMBER_VALUE;
+ case '+':
+ return PatternOptionBuilder.CLASS_VALUE;
+ case '#':
+ return PatternOptionBuilder.DATE_VALUE;
+ case '<':
+ return PatternOptionBuilder.EXISTING_FILE_VALUE;
+ case '>':
+ return PatternOptionBuilder.FILE_VALUE;
+ case '*':
+ return PatternOptionBuilder.FILES_VALUE;
+ case '/':
+ return PatternOptionBuilder.URL_VALUE;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether ch
is a value code, i.e.
+ * whether it represents a class in a pattern.
+ *
+ * @param ch the specified character
+ * @return true if ch
is a value code, otherwise false.
+ */
+ public static boolean isValueCode(char ch)
+ {
+ return ch == '@'
+ || ch == ':'
+ || ch == '%'
+ || ch == '+'
+ || ch == '#'
+ || ch == '<'
+ || ch == '>'
+ || ch == '*'
+ || ch == '/'
+ || ch == '!';
+ }
+
+ /**
+ * Returns the {@link Options} instance represented by pattern
.
+ *
+ * @param pattern the pattern string
+ * @return The {@link Options} instance
+ */
+ public static Options parsePattern(String pattern)
+ {
+ char opt = ' ';
+ boolean required = false;
+ Class> type = null;
+
+ Options options = new Options();
+
+ for (int i = 0; i < pattern.length(); i++)
+ {
+ char ch = pattern.charAt(i);
+
+ // a value code comes after an option and specifies
+ // details about it
+ if (!isValueCode(ch))
+ {
+ if (opt != ' ')
+ {
+ final Option option = Option.builder(String.valueOf(opt))
+ .hasArg(type != null)
+ .required(required)
+ .type(type)
+ .build();
+
+ // we have a previous one to deal with
+ options.addOption(option);
+ required = false;
+ type = null;
+ opt = ' ';
+ }
+
+ opt = ch;
+ }
+ else if (ch == '!')
+ {
+ required = true;
+ }
+ else
+ {
+ type = (Class>) getValueClass(ch);
+ }
+ }
+
+ if (opt != ' ')
+ {
+ final Option option = Option.builder(String.valueOf(opt))
+ .hasArg(type != null)
+ .required(required)
+ .type(type)
+ .build();
+
+ // we have a final one to deal with
+ options.addOption(option);
+ }
+
+ return options;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/PosixParser.java b/src/main/java/org/apache/commons/cli/PosixParser.java
new file mode 100644
index 00000000..24eb533b
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/PosixParser.java
@@ -0,0 +1,294 @@
+/**
+ * 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.commons.cli;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * The class PosixParser provides an implementation of the
+ * {@link Parser#flatten(Options,String[],boolean) flatten} method.
+ *
+ * @version $Id: PosixParser.java 1677451 2015-05-03 17:09:29Z ggregory $
+ * @deprecated since 1.3, use the {@link DefaultParser} instead
+ */
+@Deprecated
+public class PosixParser extends Parser
+{
+ /** holder for flattened tokens */
+ private final List tokens = new ArrayList();
+
+ /** specifies if bursting should continue */
+ private boolean eatTheRest;
+
+ /** holder for the current option */
+ private Option currentOption;
+
+ /** the command line Options */
+ private Options options;
+
+ /**
+ * Resets the members to their original state i.e. remove
+ * all of tokens
entries and set eatTheRest
+ * to false.
+ */
+ private void init()
+ {
+ eatTheRest = false;
+ tokens.clear();
+ }
+
+ /**
+ * An implementation of {@link Parser}'s abstract
+ * {@link Parser#flatten(Options,String[],boolean) flatten} method.
+ *
+ * The following are the rules used by this flatten method.
+ *
+ * if stopAtNonOption
is true then do not
+ * burst anymore of arguments
entries, just add each
+ * successive entry without further processing. Otherwise, ignore
+ * stopAtNonOption
.
+ * if the current arguments
entry is "-- "
+ * just add the entry to the list of processed tokens
+ * if the current arguments
entry is "- "
+ * just add the entry to the list of processed tokens
+ * if the current arguments
entry is two characters
+ * in length and the first character is "- " then check if this
+ * is a valid {@link Option} id. If it is a valid id, then add the
+ * entry to the list of processed tokens and set the current {@link Option}
+ * member. If it is not a valid id and stopAtNonOption
+ * is true, then the remaining entries are copied to the list of
+ * processed tokens. Otherwise, the current entry is ignored.
+ * if the current arguments
entry is more than two
+ * characters in length and the first character is "- " then
+ * we need to burst the entry to determine its constituents. For more
+ * information on the bursting algorithm see
+ * {@link PosixParser#burstToken(String, boolean) burstToken}.
+ * if the current arguments
entry is not handled
+ * by any of the previous rules, then the entry is added to the list
+ * of processed tokens.
+ *
+ *
+ * @param options The command line {@link Options}
+ * @param arguments The command line arguments to be parsed
+ * @param stopAtNonOption Specifies whether to stop flattening
+ * when an non option is found.
+ * @return The flattened arguments
String array.
+ */
+ @Override
+ protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
+ {
+ init();
+ this.options = options;
+
+ // an iterator for the command line tokens
+ Iterator iter = Arrays.asList(arguments).iterator();
+
+ // process each command line token
+ while (iter.hasNext())
+ {
+ // get the next command line token
+ String token = iter.next();
+
+ // single or double hyphen
+ if ("-".equals(token) || "--".equals(token))
+ {
+ tokens.add(token);
+ }
+
+ // handle long option --foo or --foo=bar
+ else if (token.startsWith("--"))
+ {
+ int pos = token.indexOf('=');
+ String opt = pos == -1 ? token : token.substring(0, pos); // --foo
+
+ List matchingOpts = options.getMatchingOptions(opt);
+
+ if (matchingOpts.isEmpty())
+ {
+ processNonOptionToken(token, stopAtNonOption);
+ }
+ else if (matchingOpts.size() > 1)
+ {
+ throw new AmbiguousOptionException(opt, matchingOpts);
+ }
+ else
+ {
+ currentOption = options.getOption(matchingOpts.get(0));
+
+ tokens.add("--" + currentOption.getLongOpt());
+ if (pos != -1)
+ {
+ tokens.add(token.substring(pos + 1));
+ }
+ }
+ }
+
+ else if (token.startsWith("-"))
+ {
+ if (token.length() == 2 || options.hasOption(token))
+ {
+ processOptionToken(token, stopAtNonOption);
+ }
+ else if (!options.getMatchingOptions(token).isEmpty())
+ {
+ List matchingOpts = options.getMatchingOptions(token);
+ if (matchingOpts.size() > 1)
+ {
+ throw new AmbiguousOptionException(token, matchingOpts);
+ }
+ Option opt = options.getOption(matchingOpts.get(0));
+ processOptionToken("-" + opt.getLongOpt(), stopAtNonOption);
+ }
+ // requires bursting
+ else
+ {
+ burstToken(token, stopAtNonOption);
+ }
+ }
+ else
+ {
+ processNonOptionToken(token, stopAtNonOption);
+ }
+
+ gobble(iter);
+ }
+
+ return tokens.toArray(new String[tokens.size()]);
+ }
+
+ /**
+ * Adds the remaining tokens to the processed tokens list.
+ *
+ * @param iter An iterator over the remaining tokens
+ */
+ private void gobble(Iterator iter)
+ {
+ if (eatTheRest)
+ {
+ while (iter.hasNext())
+ {
+ tokens.add(iter.next());
+ }
+ }
+ }
+
+ /**
+ * Add the special token "-- " and the current value
+ * to the processed tokens list. Then add all the remaining
+ * argument
values to the processed tokens list.
+ *
+ * @param value The current token
+ */
+ private void processNonOptionToken(String value, boolean stopAtNonOption)
+ {
+ if (stopAtNonOption && (currentOption == null || !currentOption.hasArg()))
+ {
+ eatTheRest = true;
+ tokens.add("--");
+ }
+
+ tokens.add(value);
+ }
+
+ /**
+ * If an {@link Option} exists for token
then
+ * add the token to the processed list.
+ *
+ * If an {@link Option} does not exist and stopAtNonOption
+ * is set then add the remaining tokens to the processed tokens list
+ * directly.
+ *
+ * @param token The current option token
+ * @param stopAtNonOption Specifies whether flattening should halt
+ * at the first non option.
+ */
+ private void processOptionToken(String token, boolean stopAtNonOption)
+ {
+ if (stopAtNonOption && !options.hasOption(token))
+ {
+ eatTheRest = true;
+ }
+
+ if (options.hasOption(token))
+ {
+ currentOption = options.getOption(token);
+ }
+
+ tokens.add(token);
+ }
+
+ /**
+ * Breaks token
into its constituent parts
+ * using the following algorithm.
+ *
+ *
+ * ignore the first character ("- ")
+ * foreach remaining character check if an {@link Option}
+ * exists with that id.
+ * if an {@link Option} does exist then add that character
+ * prepended with "- " to the list of processed tokens.
+ * if the {@link Option} can have an argument value and there
+ * are remaining characters in the token then add the remaining
+ * characters as a token to the list of processed tokens.
+ * if an {@link Option} does NOT exist AND
+ * stopAtNonOption
IS set then add the special token
+ * "-- " followed by the remaining characters and also
+ * the remaining tokens directly to the processed tokens list.
+ * if an {@link Option} does NOT exist AND
+ * stopAtNonOption
IS NOT set then add that
+ * character prepended with "- ".
+ *
+ *
+ * @param token The current token to be burst
+ * @param stopAtNonOption Specifies whether to stop processing
+ * at the first non-Option encountered.
+ */
+ protected void burstToken(String token, boolean stopAtNonOption)
+ {
+ for (int i = 1; i < token.length(); i++)
+ {
+ String ch = String.valueOf(token.charAt(i));
+
+ if (options.hasOption(ch))
+ {
+ tokens.add("-" + ch);
+ currentOption = options.getOption(ch);
+
+ if (currentOption.hasArg() && token.length() != i + 1)
+ {
+ tokens.add(token.substring(i + 1));
+
+ break;
+ }
+ }
+ else if (stopAtNonOption)
+ {
+ processNonOptionToken(token.substring(i), true);
+ break;
+ }
+ else
+ {
+ tokens.add(token);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/TypeHandler.java b/src/main/java/org/apache/commons/cli/TypeHandler.java
new file mode 100644
index 00000000..cc91274c
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/TypeHandler.java
@@ -0,0 +1,241 @@
+/**
+ * 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.commons.cli;
+
+import java.io.File;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.Date;
+
+/**
+ * This is a temporary implementation. TypeHandler will handle the
+ * pluggableness of OptionTypes and it will direct all of these types
+ * of conversion functionalities to ConvertUtils component in Commons
+ * already. BeanUtils I think.
+ *
+ * @version $Id: TypeHandler.java 1677452 2015-05-03 17:10:00Z ggregory $
+ */
+public class TypeHandler
+{
+ /**
+ * Returns the Object
of type obj
+ * with the value of str
.
+ *
+ * @param str the command line value
+ * @param obj the type of argument
+ * @return The instance of obj
initialised with
+ * the value of str
.
+ * @throws ParseException if the value creation for the given object type failed
+ */
+ public static Object createValue(String str, Object obj) throws ParseException
+ {
+ return createValue(str, (Class>) obj);
+ }
+
+ /**
+ * Returns the Object
of type clazz
+ * with the value of str
.
+ *
+ * @param str the command line value
+ * @param clazz the type of argument
+ * @return The instance of clazz
initialised with
+ * the value of str
.
+ * @throws ParseException if the value creation for the given class failed
+ */
+ public static Object createValue(String str, Class> clazz) throws ParseException
+ {
+ if (PatternOptionBuilder.STRING_VALUE == clazz)
+ {
+ return str;
+ }
+ else if (PatternOptionBuilder.OBJECT_VALUE == clazz)
+ {
+ return createObject(str);
+ }
+ else if (PatternOptionBuilder.NUMBER_VALUE == clazz)
+ {
+ return createNumber(str);
+ }
+ else if (PatternOptionBuilder.DATE_VALUE == clazz)
+ {
+ return createDate(str);
+ }
+ else if (PatternOptionBuilder.CLASS_VALUE == clazz)
+ {
+ return createClass(str);
+ }
+ else if (PatternOptionBuilder.FILE_VALUE == clazz)
+ {
+ return createFile(str);
+ }
+ else if (PatternOptionBuilder.EXISTING_FILE_VALUE == clazz)
+ {
+ return createFile(str);
+ }
+ else if (PatternOptionBuilder.FILES_VALUE == clazz)
+ {
+ return createFiles(str);
+ }
+ else if (PatternOptionBuilder.URL_VALUE == clazz)
+ {
+ return createURL(str);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Create an Object from the classname and empty constructor.
+ *
+ * @param classname the argument value
+ * @return the initialised object
+ * @throws ParseException if the class could not be found or the object could not be created
+ */
+ public static Object createObject(String classname) throws ParseException
+ {
+ Class> cl;
+
+ try
+ {
+ cl = Class.forName(classname);
+ }
+ catch (ClassNotFoundException cnfe)
+ {
+ throw new ParseException("Unable to find the class: " + classname);
+ }
+
+ try
+ {
+ return cl.newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ParseException(e.getClass().getName() + "; Unable to create an instance of: " + classname);
+ }
+ }
+
+ /**
+ * Create a number from a String. If a . is present, it creates a
+ * Double, otherwise a Long.
+ *
+ * @param str the value
+ * @return the number represented by str
+ * @throws ParseException if str
is not a number
+ */
+ public static Number createNumber(String str) throws ParseException
+ {
+ try
+ {
+ if (str.indexOf('.') != -1)
+ {
+ return Double.valueOf(str);
+ }
+ return Long.valueOf(str);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ParseException(e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the class whose name is classname
.
+ *
+ * @param classname the class name
+ * @return The class if it is found
+ * @throws ParseException if the class could not be found
+ */
+ public static Class> createClass(String classname) throws ParseException
+ {
+ try
+ {
+ return Class.forName(classname);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ParseException("Unable to find the class: " + classname);
+ }
+ }
+
+ /**
+ * Returns the date represented by str
.
+ *
+ * This method is not yet implemented and always throws an
+ * {@link UnsupportedOperationException}.
+ *
+ * @param str the date string
+ * @return The date if str
is a valid date string,
+ * otherwise return null.
+ * @throws UnsupportedOperationException always
+ */
+ public static Date createDate(String str)
+ {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ /**
+ * Returns the URL represented by str
.
+ *
+ * @param str the URL string
+ * @return The URL in str
is well-formed
+ * @throws ParseException if the URL in str
is not well-formed
+ */
+ public static URL createURL(String str) throws ParseException
+ {
+ try
+ {
+ return new URL(str);
+ }
+ catch (MalformedURLException e)
+ {
+ throw new ParseException("Unable to parse the URL: " + str);
+ }
+ }
+
+ /**
+ * Returns the File represented by str
.
+ *
+ * @param str the File location
+ * @return The file represented by str
.
+ */
+ public static File createFile(String str)
+ {
+ return new File(str);
+ }
+
+ /**
+ * Returns the File[] represented by str
.
+ *
+ * This method is not yet implemented and always throws an
+ * {@link UnsupportedOperationException}.
+ *
+ * @param str the paths to the files
+ * @return The File[] represented by str
.
+ * @throws UnsupportedOperationException always
+ */
+ public static File[] createFiles(String str)
+ {
+ // to implement/port:
+ // return FileW.findFiles(str);
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/UnrecognizedOptionException.java b/src/main/java/org/apache/commons/cli/UnrecognizedOptionException.java
new file mode 100644
index 00000000..4754c956
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/UnrecognizedOptionException.java
@@ -0,0 +1,71 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * Exception thrown during parsing signalling an unrecognized
+ * option was seen.
+ *
+ * @version $Id: UnrecognizedOptionException.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+public class UnrecognizedOptionException extends ParseException
+{
+ /**
+ * This exception {@code serialVersionUID}.
+ */
+ private static final long serialVersionUID = -252504690284625623L;
+
+ /** The unrecognized option */
+ private String option;
+
+ /**
+ * Construct a new UnrecognizedArgumentException
+ * with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public UnrecognizedOptionException(String message)
+ {
+ super(message);
+ }
+
+ /**
+ * Construct a new UnrecognizedArgumentException
+ * with the specified option and detail message.
+ *
+ * @param message the detail message
+ * @param option the unrecognized option
+ * @since 1.2
+ */
+ public UnrecognizedOptionException(String message, String option)
+ {
+ this(message);
+ this.option = option;
+ }
+
+ /**
+ * Returns the unrecognized option.
+ *
+ * @return the related option
+ * @since 1.2
+ */
+ public String getOption()
+ {
+ return option;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/Util.java b/src/main/java/org/apache/commons/cli/Util.java
new file mode 100644
index 00000000..89cf5ff3
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/Util.java
@@ -0,0 +1,72 @@
+/**
+ * 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.commons.cli;
+
+/**
+ * Contains useful helper methods for classes within this package.
+ *
+ * @version $Id: Util.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+final class Util
+{
+ /**
+ * Remove the hyphens from the beginning of str
and
+ * return the new String.
+ *
+ * @param str The string from which the hyphens should be removed.
+ *
+ * @return the new String.
+ */
+ static String stripLeadingHyphens(String str)
+ {
+ if (str == null)
+ {
+ return null;
+ }
+ if (str.startsWith("--"))
+ {
+ return str.substring(2, str.length());
+ }
+ else if (str.startsWith("-"))
+ {
+ return str.substring(1, str.length());
+ }
+
+ return str;
+ }
+
+ /**
+ * Remove the leading and trailing quotes from str
.
+ * E.g. if str is '"one two"', then 'one two' is returned.
+ *
+ * @param str The string from which the leading and trailing quotes
+ * should be removed.
+ *
+ * @return The string without the leading and trailing quotes.
+ */
+ static String stripLeadingAndTrailingQuotes(String str)
+ {
+ int length = str.length();
+ if (length > 1 && str.startsWith("\"") && str.endsWith("\"") && str.substring(1, length - 1).indexOf('"') == -1)
+ {
+ str = str.substring(1, length - 1);
+ }
+
+ return str;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/overview.html b/src/main/java/org/apache/commons/cli/overview.html
new file mode 100644
index 00000000..3eab8062
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/overview.html
@@ -0,0 +1,43 @@
+
+
+
+ Commons CLI -- version 1.3
+
+ The commons-cli package aides in parsing command-line arguments.
+
+ Allow command-line arguments to be parsed against a descriptor of
+ valid options (long and short), potentially with arguments.
+
+ command-line arguments may be of the typical String[]
+ form, but also may be a java.util.List
. Indexes allow
+ for parsing only a portion of the command-line. Also, functionality
+ for parsing the command-line in phases is built in, allowing for
+ 'cvs-style' command-lines, where some global options are specified
+ before a 'command' argument, and command-specific options are
+ specified after the command argument:
+
+
+
+ myApp -p <port> command -p <printer>
+
+
+
+
+
The homepage for the project is
+ Apache Commons/
+
diff --git a/src/main/java/org/apache/commons/cli/package-info.java b/src/main/java/org/apache/commons/cli/package-info.java
new file mode 100644
index 00000000..2b7dd198
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+/**
+ * Commons CLI 1.3
+ *
+ * @version $Id: package-info.java 1443102 2013-02-06 18:12:16Z tn $
+ */
+package org.apache.commons.cli;
diff --git a/src/main/java/org/apache/commons/codec/BinaryDecoder.java b/src/main/java/org/apache/commons/codec/BinaryDecoder.java
new file mode 100644
index 00000000..546ff765
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/BinaryDecoder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Defines common decoding methods for byte array decoders.
+ *
+ * @version $Id$
+ */
+public interface BinaryDecoder extends Decoder {
+
+ /**
+ * Decodes a byte array and returns the results as a byte array.
+ *
+ * @param source
+ * A byte array which has been encoded with the appropriate encoder
+ * @return a byte array that contains decoded content
+ * @throws DecoderException
+ * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
+ */
+ byte[] decode(byte[] source) throws DecoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/BinaryEncoder.java b/src/main/java/org/apache/commons/codec/BinaryEncoder.java
new file mode 100644
index 00000000..65f92cc8
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/BinaryEncoder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Defines common encoding methods for byte array encoders.
+ *
+ * @version $Id$
+ */
+public interface BinaryEncoder extends Encoder {
+
+ /**
+ * Encodes a byte array and return the encoded data as a byte array.
+ *
+ * @param source
+ * Data to be encoded
+ * @return A byte array containing the encoded data
+ * @throws EncoderException
+ * thrown if the Encoder encounters a failure condition during the encoding process.
+ */
+ byte[] encode(byte[] source) throws EncoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/CharEncoding.java b/src/main/java/org/apache/commons/codec/CharEncoding.java
new file mode 100644
index 00000000..39ec98e7
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/CharEncoding.java
@@ -0,0 +1,113 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Character encoding names required of every implementation of the Java platform.
+ *
+ * From the Java documentation Standard charsets :
+ *
+ * Every implementation of the Java platform is required to support the following character encodings. Consult the
+ * release documentation for your implementation to see if any other encodings are supported. Consult the release
+ * documentation for your implementation to see if any other encodings are supported.
+ *
+ *
+ *
+ * US-ASCII
+ * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
+ * ISO-8859-1
+ * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ * UTF-8
+ * Eight-bit Unicode Transformation Format.
+ * UTF-16BE
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ * UTF-16LE
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ * UTF-16
+ * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order
+ * accepted on input, big-endian used on output.)
+ *
+ *
+ * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not
+ * foreseen that [codec] would be made to depend on [lang].
+ *
+ *
+ * This class is immutable and thread-safe.
+ *
+ *
+ * @see Standard charsets
+ * @since 1.4
+ * @version $Id$
+ */
+public class CharEncoding {
+ /**
+ * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String ISO_8859_1 = "ISO-8859-1";
+
+ /**
+ * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String US_ASCII = "US-ASCII";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark
+ * (either order accepted on input, big-endian used on output)
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String UTF_16 = "UTF-16";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String UTF_16BE = "UTF-16BE";
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String UTF_16LE = "UTF-16LE";
+
+ /**
+ * Eight-bit Unicode Transformation Format.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ */
+ public static final String UTF_8 = "UTF-8";
+}
diff --git a/src/main/java/org/apache/commons/codec/Charsets.java b/src/main/java/org/apache/commons/codec/Charsets.java
new file mode 100644
index 00000000..d57b3af5
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/Charsets.java
@@ -0,0 +1,156 @@
+/*
+ * 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.commons.codec;
+
+import java.nio.charset.Charset;
+
+/**
+ * Charsets required of every implementation of the Java platform.
+ *
+ * From the Java documentation Standard
+ * charsets :
+ *
+ * Every implementation of the Java platform is required to support the following character encodings. Consult the
+ * release documentation for your implementation to see if any other encodings are supported. Consult the release
+ * documentation for your implementation to see if any other encodings are supported.
+ *
+ *
+ *
+ * US-ASCII
+ * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
+ * ISO-8859-1
+ * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ * UTF-8
+ * Eight-bit Unicode Transformation Format.
+ * UTF-16BE
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ * UTF-16LE
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ * UTF-16
+ * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order
+ * accepted on input, big-endian used on output.)
+ *
+ *
+ * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is
+ * not foreseen that Commons Codec would be made to depend on Commons Lang.
+ *
+ *
+ * This class is immutable and thread-safe.
+ *
+ *
+ * @see Standard charsets
+ * @since 1.7
+ * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $
+ */
+public class Charsets {
+
+ //
+ // This class should only contain Charset instances for required encodings. This guarantees that it will load
+ // correctly and without delay on all Java platforms.
+ //
+
+ /**
+ * Returns the given Charset or the default Charset if the given Charset is null.
+ *
+ * @param charset
+ * A charset or null.
+ * @return the given Charset or the default Charset if the given Charset is null
+ */
+ public static Charset toCharset(final Charset charset) {
+ return charset == null ? Charset.defaultCharset() : charset;
+ }
+
+ /**
+ * Returns a Charset for the named charset. If the name is null, return the default Charset.
+ *
+ * @param charset
+ * The name of the requested charset, may be null.
+ * @return a Charset for the named charset
+ * @throws java.nio.charset.UnsupportedCharsetException
+ * If the named charset is unavailable
+ */
+ public static Charset toCharset(final String charset) {
+ return charset == null ? Charset.defaultCharset() : Charset.forName(charset);
+ }
+
+ /**
+ * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.ISO_8859_1} instead
+ */
+ @Deprecated
+ public static final Charset ISO_8859_1 = Charset.forName(CharEncoding.ISO_8859_1);
+
+ /**
+ * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.US_ASCII} instead
+ */
+ @Deprecated
+ public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII);
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark
+ * (either order accepted on input, big-endian used on output)
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.UTF_16} instead
+ */
+ @Deprecated
+ public static final Charset UTF_16 = Charset.forName(CharEncoding.UTF_16);
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.UTF_16BE} instead
+ */
+ @Deprecated
+ public static final Charset UTF_16BE = Charset.forName(CharEncoding.UTF_16BE);
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.UTF_16LE} instead
+ */
+ @Deprecated
+ public static final Charset UTF_16LE = Charset.forName(CharEncoding.UTF_16LE);
+
+ /**
+ * Eight-bit Unicode Transformation Format.
+ *
+ * Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see Standard charsets
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets.UTF_8}
+ */
+ @Deprecated
+ public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8);
+}
diff --git a/src/main/java/org/apache/commons/codec/Decoder.java b/src/main/java/org/apache/commons/codec/Decoder.java
new file mode 100644
index 00000000..9f3ba609
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/Decoder.java
@@ -0,0 +1,47 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Provides the highest level of abstraction for Decoders.
+ *
+ * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface.
+ * Allows a user to pass a generic Object to any Decoder implementation in the codec package.
+ *
+ * One of the two interfaces at the center of the codec package.
+ *
+ * @version $Id$
+ */
+public interface Decoder {
+
+ /**
+ * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will
+ * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a
+ * {@link ClassCastException} occurs this decode method will throw a DecoderException.
+ *
+ * @param source
+ * the object to decode
+ * @return a 'decoded" object
+ * @throws DecoderException
+ * a decoder exception can be thrown for any number of reasons. Some good candidates are that the
+ * parameter passed to this method is null, a param cannot be cast to the appropriate type for a
+ * specific encoder.
+ */
+ Object decode(Object source) throws DecoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/DecoderException.java b/src/main/java/org/apache/commons/codec/DecoderException.java
new file mode 100644
index 00000000..9d51b044
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/DecoderException.java
@@ -0,0 +1,86 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder}
+ * encounters a decoding specific exception such as invalid data, or characters outside of the expected range.
+ *
+ * @version $Id$
+ */
+public class DecoderException extends Exception {
+
+ /**
+ * Declares the Serial Version Uid.
+ *
+ * @see Always Declare Serial Version Uid
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with null
as its detail message. The cause is not initialized, and may
+ * subsequently be initialized by a call to {@link #initCause}.
+ *
+ * @since 1.4
+ */
+ public DecoderException() {
+ super();
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+ * be initialized by a call to {@link #initCause}.
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ */
+ public DecoderException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ * Note that the detail message associated with cause
is not automatically incorporated into this
+ * exception's detail message.
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A null
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public DecoderException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail message of (cause==null ?
+ * null : cause.toString())
(which typically contains the class and detail message of cause
).
+ * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+ *
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A null
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public DecoderException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/Encoder.java b/src/main/java/org/apache/commons/codec/Encoder.java
new file mode 100644
index 00000000..c7e99eb1
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/Encoder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Provides the highest level of abstraction for Encoders.
+ *
+ * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this
+ * common generic interface which allows a user to pass a generic Object to any Encoder implementation
+ * in the codec package.
+ *
+ * @version $Id$
+ */
+public interface Encoder {
+
+ /**
+ * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be
+ * byte[]
or String
s depending on the implementation used.
+ *
+ * @param source
+ * An object to encode
+ * @return An "encoded" Object
+ * @throws EncoderException
+ * An encoder exception is thrown if the encoder experiences a failure condition during the encoding
+ * process.
+ */
+ Object encode(Object source) throws EncoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/EncoderException.java b/src/main/java/org/apache/commons/codec/EncoderException.java
new file mode 100644
index 00000000..7115a48d
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/EncoderException.java
@@ -0,0 +1,89 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Thrown when there is a failure condition during the encoding process. This exception is thrown when an
+ * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum,
+ * characters outside of the expected range.
+ *
+ * @version $Id$
+ */
+public class EncoderException extends Exception {
+
+ /**
+ * Declares the Serial Version Uid.
+ *
+ * @see Always Declare Serial Version Uid
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with null
as its detail message. The cause is not initialized, and may
+ * subsequently be initialized by a call to {@link #initCause}.
+ *
+ * @since 1.4
+ */
+ public EncoderException() {
+ super();
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+ * be initialized by a call to {@link #initCause}.
+ *
+ * @param message
+ * a useful message relating to the encoder specific error.
+ */
+ public EncoderException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ *
+ * Note that the detail message associated with cause
is not automatically incorporated into this
+ * exception's detail message.
+ *
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A null
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public EncoderException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail message of (cause==null ?
+ * null : cause.toString())
(which typically contains the class and detail message of cause
).
+ * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+ *
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A null
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public EncoderException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/StringDecoder.java b/src/main/java/org/apache/commons/codec/StringDecoder.java
new file mode 100644
index 00000000..c455a5ba
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/StringDecoder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Defines common decoding methods for String decoders.
+ *
+ * @version $Id$
+ */
+public interface StringDecoder extends Decoder {
+
+ /**
+ * Decodes a String and returns a String.
+ *
+ * @param source
+ * the String to decode
+ * @return the encoded String
+ * @throws DecoderException
+ * thrown if there is an error condition during the Encoding process.
+ */
+ String decode(String source) throws DecoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/StringEncoder.java b/src/main/java/org/apache/commons/codec/StringEncoder.java
new file mode 100644
index 00000000..d8b128ee
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/StringEncoder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.commons.codec;
+
+/**
+ * Defines common encoding methods for String encoders.
+ *
+ * @version $Id$
+ */
+public interface StringEncoder extends Encoder {
+
+ /**
+ * Encodes a String and returns a String.
+ *
+ * @param source
+ * the String to encode
+ * @return the encoded String
+ * @throws EncoderException
+ * thrown if there is an error condition during the encoding process.
+ */
+ String encode(String source) throws EncoderException;
+}
+
diff --git a/src/main/java/org/apache/commons/codec/StringEncoderComparator.java b/src/main/java/org/apache/commons/codec/StringEncoderComparator.java
new file mode 100644
index 00000000..2a190b44
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/StringEncoderComparator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.commons.codec;
+
+import java.util.Comparator;
+
+/**
+ * Compares Strings using a {@link StringEncoder}. This comparator is used to sort Strings by an encoding scheme such as
+ * Soundex, Metaphone, etc. This class can come in handy if one need to sort Strings by an encoded form of a name such
+ * as Soundex.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ */
+@SuppressWarnings("rawtypes")
+// TODO ought to implement Comparator but that's not possible whilst maintaining binary compatibility.
+public class StringEncoderComparator implements Comparator {
+
+ /**
+ * Internal encoder instance.
+ */
+ private final StringEncoder stringEncoder;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @deprecated Creating an instance without a {@link StringEncoder} leads to a {@link NullPointerException}. Will be
+ * removed in 2.0.
+ */
+ @Deprecated
+ public StringEncoderComparator() {
+ this.stringEncoder = null; // Trying to use this will cause things to break
+ }
+
+ /**
+ * Constructs a new instance with the given algorithm.
+ *
+ * @param stringEncoder
+ * the StringEncoder used for comparisons.
+ */
+ public StringEncoderComparator(final StringEncoder stringEncoder) {
+ this.stringEncoder = stringEncoder;
+ }
+
+ /**
+ * Compares two strings based not on the strings themselves, but on an encoding of the two strings using the
+ * StringEncoder this Comparator was created with.
+ *
+ * If an {@link EncoderException} is encountered, return 0
.
+ *
+ * @param o1
+ * the object to compare
+ * @param o2
+ * the object to compare to
+ * @return the Comparable.compareTo() return code or 0 if an encoding error was caught.
+ * @see Comparable
+ */
+ @Override
+ public int compare(final Object o1, final Object o2) {
+
+ int compareCode = 0;
+
+ try {
+ @SuppressWarnings("unchecked") // May fail with CCE if encode returns something that is not Comparable
+ // However this was always the case.
+ final Comparable> s1 = (Comparable>) this.stringEncoder.encode(o1);
+ final Comparable> s2 = (Comparable>) this.stringEncoder.encode(o2);
+ compareCode = s1.compareTo(s2);
+ } catch (final EncoderException ee) {
+ compareCode = 0;
+ }
+ return compareCode;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base32.java b/src/main/java/org/apache/commons/codec/binary/Base32.java
new file mode 100644
index 00000000..e40d6525
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base32.java
@@ -0,0 +1,539 @@
+/*
+ * 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.commons.codec.binary;
+
+/**
+ * Provides Base32 encoding and decoding as defined by RFC 4648 .
+ *
+ *
+ * The class can be parameterized in the following manner with various constructors:
+ *
+ *
+ * Whether to use the "base32hex" variant instead of the default "base32"
+ * Line length: Default 76. Line length that aren't multiples of 8 will still essentially end up being multiples of
+ * 8 in the encoded data.
+ * Line separator: Default is CRLF ("\r\n")
+ *
+ *
+ * This class operates directly on byte streams, and not character streams.
+ *
+ *
+ * This class is thread-safe.
+ *
+ *
+ * @see RFC 4648
+ *
+ * @since 1.5
+ * @version $Id$
+ */
+public class Base32 extends BaseNCodec {
+
+ /**
+ * BASE32 characters are 5 bits in length.
+ * They are formed by taking a block of five octets to form a 40-bit string,
+ * which is converted into eight BASE32 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 5;
+ private static final int BYTES_PER_ENCODED_BLOCK = 8;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 5;
+
+ /**
+ * Chunk separator per RFC 2045 section 2.1.
+ *
+ * @see RFC 2045 section 2.1
+ */
+ private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
+
+ /**
+ * This array is a lookup table that translates Unicode characters drawn from the "Base32 Alphabet" (as specified
+ * in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the Base32
+ * alphabet but fall within the bounds of the array are translated to -1.
+ */
+ private static final byte[] DECODE_TABLE = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
+ -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, // 30-3f 2-7
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 50-5a P-Z
+ };
+
+ /**
+ * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet"
+ * equivalents as specified in Table 3 of RFC 4648.
+ */
+ private static final byte[] ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ '2', '3', '4', '5', '6', '7',
+ };
+
+ /**
+ * This array is a lookup table that translates Unicode characters drawn from the "Base32 Hex Alphabet" (as
+ * specified in Table 4 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the
+ * Base32 Hex alphabet but fall within the bounds of the array are translated to -1.
+ */
+ private static final byte[] HEX_DECODE_TABLE = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 2-7
+ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40-4f A-O
+ 25, 26, 27, 28, 29, 30, 31, // 50-57 P-V
+ };
+
+ /**
+ * This array is a lookup table that translates 5-bit positive integer index values into their
+ * "Base32 Hex Alphabet" equivalents as specified in Table 4 of RFC 4648.
+ */
+ private static final byte[] HEX_ENCODE_TABLE = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ };
+
+ /** Mask used to extract 5 bits, used when encoding Base32 bytes */
+ private static final int MASK_5BITS = 0x1f;
+
+ // The static final fields above are used for the original static byte[] methods on Base32.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length;
+ */
+ private final int decodeSize;
+
+ /**
+ * Decode table to use.
+ */
+ private final byte[] decodeTable;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length;
+ */
+ private final int encodeSize;
+
+ /**
+ * Encode table to use.
+ */
+ private final byte[] encodeTable;
+
+ /**
+ * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
+ */
+ private final byte[] lineSeparator;
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ *
+ */
+ public Base32() {
+ this(false);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ * @param pad byte used as padding byte.
+ */
+ public Base32(final byte pad) {
+ this(false, pad);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ * @param useHex if {@code true} then use Base32 Hex alphabet
+ */
+ public Base32(final boolean useHex) {
+ this(0, null, useHex, PAD_DEFAULT);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ * @param useHex if {@code true} then use Base32 Hex alphabet
+ * @param pad byte used as padding byte.
+ */
+ public Base32(final boolean useHex, final byte pad) {
+ this(0, null, useHex, pad);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is given in the constructor, the line separator is CRLF.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ */
+ public Base32(final int lineLength) {
+ this(lineLength, CHUNK_SEPARATOR);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length and line separator are given in the constructor.
+ *
+ *
+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some Base32 characters. That's not going to work!
+ */
+ public Base32(final int lineLength, final byte[] lineSeparator) {
+ this(lineLength, lineSeparator, false, PAD_DEFAULT);
+ }
+
+ /**
+ * Creates a Base32 / Base32 Hex codec used for decoding and encoding.
+ *
+ * When encoding the line length and line separator are given in the constructor.
+ *
+ *
+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @param useHex
+ * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some Base32 characters. That's not going to work! Or the
+ * lineLength > 0 and lineSeparator is null.
+ */
+ public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) {
+ this(lineLength, lineSeparator, useHex, PAD_DEFAULT);
+ }
+
+ /**
+ * Creates a Base32 / Base32 Hex codec used for decoding and encoding.
+ *
+ * When encoding the line length and line separator are given in the constructor.
+ *
+ *
+ * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @param useHex
+ * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet
+ * @param pad byte used as padding byte.
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some Base32 characters. That's not going to work! Or the
+ * lineLength > 0 and lineSeparator is null.
+ */
+ public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) {
+ super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength,
+ lineSeparator == null ? 0 : lineSeparator.length, pad);
+ if (useHex) {
+ this.encodeTable = HEX_ENCODE_TABLE;
+ this.decodeTable = HEX_DECODE_TABLE;
+ } else {
+ this.encodeTable = ENCODE_TABLE;
+ this.decodeTable = DECODE_TABLE;
+ }
+ if (lineLength > 0) {
+ if (lineSeparator == null) {
+ throw new IllegalArgumentException("lineLength " + lineLength + " > 0, but lineSeparator is null");
+ }
+ // Must be done after initializing the tables
+ if (containsAlphabetOrPad(lineSeparator)) {
+ final String sep = StringUtils.newStringUtf8(lineSeparator);
+ throw new IllegalArgumentException("lineSeparator must not contain Base32 characters: [" + sep + "]");
+ }
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
+ this.lineSeparator = new byte[lineSeparator.length];
+ System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ this.decodeSize = this.encodeSize - 1;
+
+ if (isInAlphabet(pad) || isWhiteSpace(pad)) {
+ throw new IllegalArgumentException("pad must not be in alphabet or whitespace");
+ }
+ }
+
+ /**
+ *
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ *
+ *
+ * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ *
+ *
+ * @param in
+ * byte[] array of ascii data to Base32 decode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context the context to be used
+ *
+ * Output is written to {@link Context#buffer} as 8-bit octets, using {@link Context#pos} as the buffer position
+ */
+ @Override
+ void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ // package protected for access from I/O streams
+
+ if (context.eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ context.eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ final byte b = in[inPos++];
+ if (b == pad) {
+ // We're done.
+ context.eof = true;
+ break;
+ } else {
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+ if (b >= 0 && b < this.decodeTable.length) {
+ final int result = this.decodeTable[b];
+ if (result >= 0) {
+ context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
+ // collect decoded bytes
+ context.lbitWorkArea = (context.lbitWorkArea << BITS_PER_ENCODED_BYTE) + result;
+ if (context.modulus == 0) { // we can output the 5 bytes
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 32) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) (context.lbitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as Base32 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (context.eof && context.modulus >= 2) { // if modulus < 2, nothing to do
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+
+ // we ignore partial bytes, i.e. only multiples of 8 count
+ switch (context.modulus) {
+ case 2 : // 10 bits, drop 2 and output one byte
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 2) & MASK_8BITS);
+ break;
+ case 3 : // 15 bits, drop 7 and output 1 byte
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 7) & MASK_8BITS);
+ break;
+ case 4 : // 20 bits = 2*8 + 4
+ context.lbitWorkArea = context.lbitWorkArea >> 4; // drop 4 bits
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS);
+ break;
+ case 5 : // 25bits = 3*8 + 1
+ context.lbitWorkArea = context.lbitWorkArea >> 1;
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS);
+ break;
+ case 6 : // 30bits = 3*8 + 6
+ context.lbitWorkArea = context.lbitWorkArea >> 6;
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS);
+ break;
+ case 7 : // 35 = 4*8 +3
+ context.lbitWorkArea = context.lbitWorkArea >> 3;
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS);
+ break;
+ default:
+ // modulus can be 0-7, and we excluded 0,1 already
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ }
+ }
+
+ /**
+ *
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last
+ * remaining bytes (if not multiple of 5).
+ *
+ *
+ * @param in
+ * byte[] array of binary data to Base32 encode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context the context to be used
+ */
+ @Override
+ void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ // package protected for access from I/O streams
+
+ if (context.eof) {
+ return;
+ }
+ // inAvail < 0 is how we're informed of EOF in the underlying data we're
+ // encoding.
+ if (inAvail < 0) {
+ context.eof = true;
+ if (0 == context.modulus && lineLength == 0) {
+ return; // no leftovers to process and not using chunking
+ }
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ final int savedPos = context.pos;
+ switch (context.modulus) { // % 5
+ case 0 :
+ break;
+ case 1 : // Only 1 octet; take top 5 bits then remainder
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ break;
+ case 2 : // 2 octets = 16 bits to use
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ break;
+ case 3 : // 3 octets = 24 bits to use
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ break;
+ case 4 : // 4 octets = 32 bits to use
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3
+ buffer[context.pos++] = pad;
+ break;
+ default:
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ context.currentLinePos += context.pos - savedPos; // keep track of current line position
+ // if currentPos == 0 we are at the start of a line, so don't add CRLF
+ if (lineLength > 0 && context.currentLinePos > 0){ // add chunk separator if required
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ context.lbitWorkArea = (context.lbitWorkArea << 8) + b; // BITS_PER_BYTE
+ if (0 == context.modulus) { // we have enough bytes to create our output
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 35) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 30) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 25) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 20) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 15) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 10) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 5) & MASK_5BITS];
+ buffer[context.pos++] = encodeTable[(int)context.lbitWorkArea & MASK_5BITS];
+ context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
+ if (lineLength > 0 && lineLength <= context.currentLinePos) {
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ context.currentLinePos = 0;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not the {@code octet} is in the Base32 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return {@code true} if the value is defined in the the Base32 alphabet {@code false} otherwise.
+ */
+ @Override
+ public boolean isInAlphabet(final byte octet) {
+ return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base32InputStream.java b/src/main/java/org/apache/commons/codec/binary/Base32InputStream.java
new file mode 100644
index 00000000..844f9a06
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base32InputStream.java
@@ -0,0 +1,85 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.io.InputStream;
+
+/**
+ * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength
+ * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ *
+ * The default behaviour of the Base32InputStream is to DECODE, whereas the default behaviour of the Base32OutputStream
+ * is to ENCODE, but this behaviour can be overridden by using a different constructor.
+ *
+ *
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode
+ * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
+ *
+ *
+ * @version $Id$
+ * @see RFC 4648
+ * @since 1.5
+ */
+public class Base32InputStream extends BaseNCodecInputStream {
+
+ /**
+ * Creates a Base32InputStream such that all data read is Base32-decoded from the original provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ */
+ public Base32InputStream(final InputStream in) {
+ this(in, false);
+ }
+
+ /**
+ * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original
+ * provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data read from us, false if we should decode.
+ */
+ public Base32InputStream(final InputStream in, final boolean doEncode) {
+ super(in, new Base32(false), doEncode);
+ }
+
+ /**
+ * Creates a Base32InputStream such that all data read is either Base32-encoded or Base32-decoded from the original
+ * provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data read from us, false if we should decode.
+ * @param lineLength
+ * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to
+ * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode
+ * is false, lineLength is ignored.
+ * @param lineSeparator
+ * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n).
+ * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored.
+ */
+ public Base32InputStream(final InputStream in, final boolean doEncode,
+ final int lineLength, final byte[] lineSeparator) {
+ super(in, new Base32(lineLength, lineSeparator), doEncode);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base32OutputStream.java b/src/main/java/org/apache/commons/codec/binary/Base32OutputStream.java
new file mode 100644
index 00000000..b2319315
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base32OutputStream.java
@@ -0,0 +1,89 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.io.OutputStream;
+
+/**
+ * Provides Base32 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength
+ * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ *
+ * The default behaviour of the Base32OutputStream is to ENCODE, whereas the default behaviour of the Base32InputStream
+ * is to DECODE. But this behaviour can be overridden by using a different constructor.
+ *
+ *
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode
+ * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
+ *
+ *
+ * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the
+ * final padding will be omitted and the resulting data will be incomplete/inconsistent.
+ *
+ *
+ * @version $Id$
+ * @see RFC 4648
+ * @since 1.5
+ */
+public class Base32OutputStream extends BaseNCodecOutputStream {
+
+ /**
+ * Creates a Base32OutputStream such that all data written is Base32-encoded to the original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ */
+ public Base32OutputStream(final OutputStream out) {
+ this(out, true);
+ }
+
+ /**
+ * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the
+ * original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data written to us, false if we should decode.
+ */
+ public Base32OutputStream(final OutputStream out, final boolean doEncode) {
+ super(out, new Base32(false), doEncode);
+ }
+
+ /**
+ * Creates a Base32OutputStream such that all data written is either Base32-encoded or Base32-decoded to the
+ * original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data written to us, false if we should decode.
+ * @param lineLength
+ * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to
+ * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode
+ * is false, lineLength is ignored.
+ * @param lineSeparator
+ * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n).
+ * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored.
+ */
+ public Base32OutputStream(final OutputStream out, final boolean doEncode,
+ final int lineLength, final byte[] lineSeparator) {
+ super(out, new Base32(lineLength, lineSeparator), doEncode);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base64.java b/src/main/java/org/apache/commons/codec/binary/Base64.java
new file mode 100644
index 00000000..742c433c
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base64.java
@@ -0,0 +1,786 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.math.BigInteger;
+
+/**
+ * Provides Base64 encoding and decoding as defined by RFC 2045 .
+ *
+ *
+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein.
+ *
+ *
+ * The class can be parameterized in the following manner with various constructors:
+ *
+ *
+ * URL-safe mode: Default off.
+ * Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of
+ * 4 in the encoded data.
+ * Line separator: Default is CRLF ("\r\n")
+ *
+ *
+ * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes.
+ *
+ *
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only
+ * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252,
+ * UTF-8, etc).
+ *
+ *
+ * This class is thread-safe.
+ *
+ *
+ * @see RFC 2045
+ * @since 1.0
+ * @version $Id$
+ */
+public class Base64 extends BaseNCodec {
+
+ /**
+ * BASE32 characters are 6 bits in length.
+ * They are formed by taking a block of 3 octets to form a 24-bit string,
+ * which is converted into 4 BASE64 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 6;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 3;
+ private static final int BYTES_PER_ENCODED_BLOCK = 4;
+
+ /**
+ * Chunk separator per RFC 2045 section 2.1.
+ *
+ *
+ * N.B. The next major release may break compatibility and make this field private.
+ *
+ *
+ * @see RFC 2045 section 2.1
+ */
+ static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
+
+ /**
+ * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet"
+ * equivalents as specified in Table 1 of RFC 2045.
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] STANDARD_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ /**
+ * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
+ * changed to - and _ to make the encoded Base64 results more URL-SAFE.
+ * This table is only used when the Base64's mode is set to URL-SAFE.
+ */
+ private static final byte[] URL_SAFE_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
+ };
+
+ /**
+ * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified
+ * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
+ * alphabet but fall within the bounds of the array are translated to -1.
+ *
+ * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
+ * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit).
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] DECODE_TABLE = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - /
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z
+ };
+
+ /**
+ * Base64 uses 6-bit fields.
+ */
+ /** Mask used to extract 6 bits, used when encoding */
+ private static final int MASK_6BITS = 0x3f;
+
+ // The static final fields above are used for the original static byte[] methods on Base64.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able
+ * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch
+ * between the two modes.
+ */
+ private final byte[] encodeTable;
+
+ // Only one decode table currently; keep for consistency with Base32 code
+ private final byte[] decodeTable = DECODE_TABLE;
+
+ /**
+ * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
+ */
+ private final byte[] lineSeparator;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * decodeSize = 3 + lineSeparator.length;
+ */
+ private final int decodeSize;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * encodeSize = 4 + lineSeparator.length;
+ */
+ private final int encodeSize;
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE.
+ *
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ */
+ public Base64() {
+ this(0);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode.
+ *
+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE.
+ *
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ *
+ * @param urlSafe
+ * if true
, URL-safe encoding is used. In most cases this should be set to
+ * false
.
+ * @since 1.4
+ */
+ public Base64(final boolean urlSafe) {
+ this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ *
+ *
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @since 1.4
+ */
+ public Base64(final int lineLength) {
+ this(lineLength, CHUNK_SEPARATOR);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length and line separator are given in the constructor, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ *
+ *
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @throws IllegalArgumentException
+ * Thrown when the provided lineSeparator included some base64 characters.
+ * @since 1.4
+ */
+ public Base64(final int lineLength, final byte[] lineSeparator) {
+ this(lineLength, lineSeparator, false);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length and line separator are given in the constructor, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ *
+ *
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @param urlSafe
+ * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode
+ * operations. Decoding seamlessly handles both modes.
+ * Note: no padding is added when using the URL-safe alphabet.
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some base64 characters. That's not going to work!
+ * @since 1.4
+ */
+ public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) {
+ super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
+ lineLength,
+ lineSeparator == null ? 0 : lineSeparator.length);
+ // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
+ // @see test case Base64Test.testConstructors()
+ if (lineSeparator != null) {
+ if (containsAlphabetOrPad(lineSeparator)) {
+ final String sep = StringUtils.newStringUtf8(lineSeparator);
+ throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
+ }
+ if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
+ this.lineSeparator = new byte[lineSeparator.length];
+ System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ this.decodeSize = this.encodeSize - 1;
+ this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
+ }
+
+ /**
+ * Returns our current encode mode. True if we're URL-SAFE, false otherwise.
+ *
+ * @return true if we're in URL-SAFE mode, false otherwise.
+ * @since 1.4
+ */
+ public boolean isUrlSafe() {
+ return this.encodeTable == URL_SAFE_ENCODE_TABLE;
+ }
+
+ /**
+ *
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last
+ * remaining bytes (if not multiple of 3).
+ *
+ * Note: no padding is added when encoding using the URL-safe alphabet.
+ *
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ *
+ *
+ * @param in
+ * byte[] array of binary data to base64 encode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context
+ * the context to be used
+ */
+ @Override
+ void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ if (context.eof) {
+ return;
+ }
+ // inAvail < 0 is how we're informed of EOF in the underlying data we're
+ // encoding.
+ if (inAvail < 0) {
+ context.eof = true;
+ if (0 == context.modulus && lineLength == 0) {
+ return; // no leftovers to process and not using chunking
+ }
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ final int savedPos = context.pos;
+ switch (context.modulus) { // 0-2
+ case 0 : // nothing to do here
+ break;
+ case 1 : // 8 bits = 6 + 2
+ // top 6 bits:
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS];
+ // remaining 2:
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[context.pos++] = pad;
+ buffer[context.pos++] = pad;
+ }
+ break;
+
+ case 2 : // 16 bits = 6 + 6 + 4
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[context.pos++] = pad;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ context.currentLinePos += context.pos - savedPos; // keep track of current line position
+ // if currentPos == 0 we are at the start of a line, so don't add CRLF
+ if (lineLength > 0 && context.currentLinePos > 0) {
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE
+ if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
+ context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
+ if (lineLength > 0 && lineLength <= context.currentLinePos) {
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ context.currentLinePos = 0;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ *
+ *
+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ *
+ *
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ *
+ *
+ * @param in
+ * byte[] array of ascii data to base64 decode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context
+ * the context to be used
+ */
+ @Override
+ void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ if (context.eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ context.eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+ final byte b = in[inPos++];
+ if (b == pad) {
+ // We're done.
+ context.eof = true;
+ break;
+ } else {
+ if (b >= 0 && b < DECODE_TABLE.length) {
+ final int result = DECODE_TABLE[b];
+ if (result >= 0) {
+ context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
+ context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
+ if (context.modulus == 0) {
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as base64 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (context.eof && context.modulus != 0) {
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+
+ // We have some spare bits remaining
+ // Output all whole multiples of 8 bits and ignore the rest
+ switch (context.modulus) {
+// case 0 : // impossible, as excluded above
+ case 1 : // 6 bits - ignore entirely
+ // TODO not currently tested; perhaps it is impossible?
+ break;
+ case 2 : // 12 bits = 8 + 4
+ context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+ break;
+ case 3 : // 18 bits = 8 + 8 + 2
+ context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+ break;
+ default:
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ }
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return true
if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+ * false
, otherwise
+ * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0.
+ */
+ @Deprecated
+ public static boolean isArrayByteBase64(final byte[] arrayOctet) {
+ return isBase64(arrayOctet);
+ }
+
+ /**
+ * Returns whether or not the octet
is in the base 64 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return true
if the value is defined in the the base 64 alphabet, false
otherwise.
+ * @since 1.4
+ */
+ public static boolean isBase64(final byte octet) {
+ return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param base64
+ * String to test
+ * @return true
if all characters in the String are valid characters in the Base64 alphabet or if
+ * the String is empty; false
, otherwise
+ * @since 1.5
+ */
+ public static boolean isBase64(final String base64) {
+ return isBase64(StringUtils.getBytesUtf8(base64));
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return true
if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+ * false
, otherwise
+ * @since 1.5
+ */
+ public static boolean isBase64(final byte[] arrayOctet) {
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm but does not chunk the output.
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return byte[] containing Base64 characters in their UTF-8 representation.
+ */
+ public static byte[] encodeBase64(final byte[] binaryData) {
+ return encodeBase64(binaryData, false);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm but does not chunk the output.
+ *
+ * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to
+ * single-line non-chunking (commons-codec-1.5).
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return String containing Base64 characters.
+ * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not).
+ */
+ public static String encodeBase64String(final byte[] binaryData) {
+ return StringUtils.newStringUtf8(encodeBase64(binaryData, false));
+ }
+
+ /**
+ * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+ * url-safe variation emits - and _ instead of + and / characters.
+ * Note: no padding is added.
+ * @param binaryData
+ * binary data to encode
+ * @return byte[] containing Base64 characters in their UTF-8 representation.
+ * @since 1.4
+ */
+ public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
+ return encodeBase64(binaryData, false, true);
+ }
+
+ /**
+ * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+ * url-safe variation emits - and _ instead of + and / characters.
+ * Note: no padding is added.
+ * @param binaryData
+ * binary data to encode
+ * @return String containing Base64 characters
+ * @since 1.4
+ */
+ public static String encodeBase64URLSafeString(final byte[] binaryData) {
+ return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true));
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return Base64 characters chunked in 76 character blocks
+ */
+ public static byte[] encodeBase64Chunked(final byte[] binaryData) {
+ return encodeBase64(binaryData, true);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if true
this encoder will chunk the base64 output into 76 character blocks
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) {
+ return encodeBase64(binaryData, isChunked, false);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if true
this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if true
this encoder will emit - and _ instead of the usual + and / characters.
+ * Note: no padding is added when encoding using the URL-safe alphabet.
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+ * @since 1.4
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) {
+ return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if true
this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if true
this encoder will emit - and _ instead of the usual + and / characters.
+ * Note: no padding is added when encoding using the URL-safe alphabet.
+ * @param maxResultSize
+ * The maximum result size to accept.
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than maxResultSize
+ * @since 1.4
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked,
+ final boolean urlSafe, final int maxResultSize) {
+ if (binaryData == null || binaryData.length == 0) {
+ return binaryData;
+ }
+
+ // Create this so can use the super-class method
+ // Also ensures that the same roundings are performed by the ctor and the code
+ final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
+ final long len = b64.getEncodedLength(binaryData);
+ if (len > maxResultSize) {
+ throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
+ len +
+ ") than the specified maximum size of " +
+ maxResultSize);
+ }
+
+ return b64.encode(binaryData);
+ }
+
+ /**
+ * Decodes a Base64 String into octets.
+ *
+ * Note: this method seamlessly handles data encoded in URL-safe or normal mode.
+ *
+ *
+ * @param base64String
+ * String containing Base64 data
+ * @return Array containing decoded data.
+ * @since 1.4
+ */
+ public static byte[] decodeBase64(final String base64String) {
+ return new Base64().decode(base64String);
+ }
+
+ /**
+ * Decodes Base64 data into octets.
+ *
+ * Note: this method seamlessly handles data encoded in URL-safe or normal mode.
+ *
+ *
+ * @param base64Data
+ * Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decodeBase64(final byte[] base64Data) {
+ return new Base64().decode(base64Data);
+ }
+
+ // Implementation of the Encoder Interface
+
+ // Implementation of integer encoding used for crypto
+ /**
+ * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature.
+ *
+ * @param pArray
+ * a byte array containing base64 character data
+ * @return A BigInteger
+ * @since 1.4
+ */
+ public static BigInteger decodeInteger(final byte[] pArray) {
+ return new BigInteger(1, decodeBase64(pArray));
+ }
+
+ /**
+ * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature.
+ *
+ * @param bigInt
+ * a BigInteger
+ * @return A byte array containing base64 character data
+ * @throws NullPointerException
+ * if null is passed in
+ * @since 1.4
+ */
+ public static byte[] encodeInteger(final BigInteger bigInt) {
+ if (bigInt == null) {
+ throw new NullPointerException("encodeInteger called with null parameter");
+ }
+ return encodeBase64(toIntegerBytes(bigInt), false);
+ }
+
+ /**
+ * Returns a byte-array representation of a BigInteger
without sign bit.
+ *
+ * @param bigInt
+ * BigInteger
to be converted
+ * @return a byte array representation of the BigInteger parameter
+ */
+ static byte[] toIntegerBytes(final BigInteger bigInt) {
+ int bitlen = bigInt.bitLength();
+ // round bitlen
+ bitlen = ((bitlen + 7) >> 3) << 3;
+ final byte[] bigBytes = bigInt.toByteArray();
+
+ if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
+ return bigBytes;
+ }
+ // set up params for copying everything but sign bit
+ int startSrc = 0;
+ int len = bigBytes.length;
+
+ // if bigInt is exactly byte-aligned, just skip signbit in copy
+ if ((bigInt.bitLength() % 8) == 0) {
+ startSrc = 1;
+ len--;
+ }
+ final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
+ final byte[] resizedBytes = new byte[bitlen / 8];
+ System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
+ return resizedBytes;
+ }
+
+ /**
+ * Returns whether or not the octet
is in the Base64 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return true
if the value is defined in the the Base64 alphabet false
otherwise.
+ */
+ @Override
+ protected boolean isInAlphabet(final byte octet) {
+ return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base64InputStream.java b/src/main/java/org/apache/commons/codec/binary/Base64InputStream.java
new file mode 100644
index 00000000..5d926bde
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base64InputStream.java
@@ -0,0 +1,88 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.io.InputStream;
+
+/**
+ * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength
+ * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ *
+ * The default behaviour of the Base64InputStream is to DECODE, whereas the default behaviour of the Base64OutputStream
+ * is to ENCODE, but this behaviour can be overridden by using a different constructor.
+ *
+ *
+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein.
+ *
+ *
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode
+ * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
+ *
+ *
+ * @version $Id$
+ * @see RFC 2045
+ * @since 1.4
+ */
+public class Base64InputStream extends BaseNCodecInputStream {
+
+ /**
+ * Creates a Base64InputStream such that all data read is Base64-decoded from the original provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ */
+ public Base64InputStream(final InputStream in) {
+ this(in, false);
+ }
+
+ /**
+ * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original
+ * provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data read from us, false if we should decode.
+ */
+ public Base64InputStream(final InputStream in, final boolean doEncode) {
+ super(in, new Base64(false), doEncode);
+ }
+
+ /**
+ * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original
+ * provided InputStream.
+ *
+ * @param in
+ * InputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data read from us, false if we should decode.
+ * @param lineLength
+ * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to
+ * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode
+ * is false, lineLength is ignored.
+ * @param lineSeparator
+ * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n).
+ * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored.
+ */
+ public Base64InputStream(final InputStream in, final boolean doEncode,
+ final int lineLength, final byte[] lineSeparator) {
+ super(in, new Base64(lineLength, lineSeparator), doEncode);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Base64OutputStream.java b/src/main/java/org/apache/commons/codec/binary/Base64OutputStream.java
new file mode 100644
index 00000000..35d3e3c3
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Base64OutputStream.java
@@ -0,0 +1,92 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.io.OutputStream;
+
+/**
+ * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength
+ * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ *
+ * The default behaviour of the Base64OutputStream is to ENCODE, whereas the default behaviour of the Base64InputStream
+ * is to DECODE. But this behaviour can be overridden by using a different constructor.
+ *
+ *
+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein.
+ *
+ *
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode
+ * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
+ *
+ *
+ * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the
+ * final padding will be omitted and the resulting data will be incomplete/inconsistent.
+ *
+ *
+ * @version $Id$
+ * @see RFC 2045
+ * @since 1.4
+ */
+public class Base64OutputStream extends BaseNCodecOutputStream {
+
+ /**
+ * Creates a Base64OutputStream such that all data written is Base64-encoded to the original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ */
+ public Base64OutputStream(final OutputStream out) {
+ this(out, true);
+ }
+
+ /**
+ * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the
+ * original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data written to us, false if we should decode.
+ */
+ public Base64OutputStream(final OutputStream out, final boolean doEncode) {
+ super(out,new Base64(false), doEncode);
+ }
+
+ /**
+ * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the
+ * original provided OutputStream.
+ *
+ * @param out
+ * OutputStream to wrap.
+ * @param doEncode
+ * true if we should encode all data written to us, false if we should decode.
+ * @param lineLength
+ * If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to
+ * nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If doEncode
+ * is false, lineLength is ignored.
+ * @param lineSeparator
+ * If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n).
+ * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored.
+ */
+ public Base64OutputStream(final OutputStream out, final boolean doEncode,
+ final int lineLength, final byte[] lineSeparator) {
+ super(out, new Base64(lineLength, lineSeparator), doEncode);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/BaseNCodec.java b/src/main/java/org/apache/commons/codec/binary/BaseNCodec.java
new file mode 100644
index 00000000..8d73442a
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/BaseNCodec.java
@@ -0,0 +1,525 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.util.Arrays;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+
+/**
+ * Abstract superclass for Base-N encoders and decoders.
+ *
+ *
+ * This class is thread-safe.
+ *
+ *
+ * @version $Id$
+ */
+public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder {
+
+ /**
+ * Holds thread context so classes can be thread-safe.
+ *
+ * This class is not itself thread-safe; each thread must allocate its own copy.
+ *
+ * @since 1.7
+ */
+ static class Context {
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ int ibitWorkArea;
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ long lbitWorkArea;
+
+ /**
+ * Buffer for streaming.
+ */
+ byte[] buffer;
+
+ /**
+ * Position where next character should be written in the buffer.
+ */
+ int pos;
+
+ /**
+ * Position where next character should be read from the buffer.
+ */
+ int readPos;
+
+ /**
+ * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+ * and must be thrown away.
+ */
+ boolean eof;
+
+ /**
+ * Variable tracks how many characters have been written to the current line. Only used when encoding. We use
+ * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0).
+ */
+ int currentLinePos;
+
+ /**
+ * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This
+ * variable helps track that.
+ */
+ int modulus;
+
+ Context() {
+ }
+
+ /**
+ * Returns a String useful for debugging (especially within a debugger.)
+ *
+ * @return a String useful for debugging.
+ */
+ @SuppressWarnings("boxing") // OK to ignore boxing here
+ @Override
+ public String toString() {
+ return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " +
+ "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), Arrays.toString(buffer),
+ currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos);
+ }
+ }
+
+ /**
+ * EOF
+ *
+ * @since 1.7
+ */
+ static final int EOF = -1;
+
+ /**
+ * MIME chunk size per RFC 2045 section 6.8.
+ *
+ *
+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+ * equal signs.
+ *
+ *
+ * @see RFC 2045 section 6.8
+ */
+ public static final int MIME_CHUNK_SIZE = 76;
+
+ /**
+ * PEM chunk size per RFC 1421 section 4.3.2.4.
+ *
+ *
+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+ * equal signs.
+ *
+ *
+ * @see RFC 1421 section 4.3.2.4
+ */
+ public static final int PEM_CHUNK_SIZE = 64;
+
+ private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+
+ /**
+ * Defines the default buffer size - currently {@value}
+ * - must be large enough for at least one encoded block+separator
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ /** Mask used to extract 8 bits, used in decoding bytes */
+ protected static final int MASK_8BITS = 0xff;
+
+ /**
+ * Byte used to pad output.
+ */
+ protected static final byte PAD_DEFAULT = '='; // Allow static access to default
+
+ /**
+ * @deprecated Use {@link #pad}. Will be removed in 2.0.
+ */
+ @Deprecated
+ protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later
+
+ protected final byte pad; // instance variable just in case it needs to vary later
+
+ /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
+ private final int unencodedBlockSize;
+
+ /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
+ private final int encodedBlockSize;
+
+ /**
+ * Chunksize for encoding. Not used when decoding.
+ * A value of zero or less implies no chunking of the encoded data.
+ * Rounded down to nearest multiple of encodedBlockSize.
+ */
+ protected final int lineLength;
+
+ /**
+ * Size of chunk separator. Not used unless {@link #lineLength} > 0.
+ */
+ private final int chunkSeparatorLength;
+
+ /**
+ * Note lineLength
is rounded down to the nearest multiple of {@link #encodedBlockSize}
+ * If chunkSeparatorLength
is zero, then chunking is disabled.
+ * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
+ * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
+ * @param lineLength if > 0, use chunking with a length lineLength
+ * @param chunkSeparatorLength the chunk separator length, if relevant
+ */
+ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
+ final int lineLength, final int chunkSeparatorLength) {
+ this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT);
+ }
+
+ /**
+ * Note lineLength
is rounded down to the nearest multiple of {@link #encodedBlockSize}
+ * If chunkSeparatorLength
is zero, then chunking is disabled.
+ * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
+ * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
+ * @param lineLength if > 0, use chunking with a length lineLength
+ * @param chunkSeparatorLength the chunk separator length, if relevant
+ * @param pad byte used as padding byte.
+ */
+ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
+ final int lineLength, final int chunkSeparatorLength, final byte pad) {
+ this.unencodedBlockSize = unencodedBlockSize;
+ this.encodedBlockSize = encodedBlockSize;
+ final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
+ this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
+ this.chunkSeparatorLength = chunkSeparatorLength;
+
+ this.pad = pad;
+ }
+
+ /**
+ * Returns true if this object has buffered data for reading.
+ *
+ * @param context the context to be used
+ * @return true if there is data still available for reading.
+ */
+ boolean hasData(final Context context) { // package protected for access from I/O streams
+ return context.buffer != null;
+ }
+
+ /**
+ * Returns the amount of buffered data available for reading.
+ *
+ * @param context the context to be used
+ * @return The amount of buffered data available for reading.
+ */
+ int available(final Context context) { // package protected for access from I/O streams
+ return context.buffer != null ? context.pos - context.readPos : 0;
+ }
+
+ /**
+ * Get the default buffer size. Can be overridden.
+ *
+ * @return {@link #DEFAULT_BUFFER_SIZE}
+ */
+ protected int getDefaultBufferSize() {
+ return DEFAULT_BUFFER_SIZE;
+ }
+
+ /**
+ * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
+ * @param context the context to be used
+ */
+ private byte[] resizeBuffer(final Context context) {
+ if (context.buffer == null) {
+ context.buffer = new byte[getDefaultBufferSize()];
+ context.pos = 0;
+ context.readPos = 0;
+ } else {
+ final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+ System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
+ context.buffer = b;
+ }
+ return context.buffer;
+ }
+
+ /**
+ * Ensure that the buffer has room for size
bytes
+ *
+ * @param size minimum spare space required
+ * @param context the context to be used
+ * @return the buffer
+ */
+ protected byte[] ensureBufferSize(final int size, final Context context){
+ if ((context.buffer == null) || (context.buffer.length < context.pos + size)){
+ return resizeBuffer(context);
+ }
+ return context.buffer;
+ }
+
+ /**
+ * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail
+ * bytes. Returns how many bytes were actually extracted.
+ *
+ * Package protected for access from I/O streams.
+ *
+ * @param b
+ * byte[] array to extract the buffered data into.
+ * @param bPos
+ * position in byte[] array to start extraction at.
+ * @param bAvail
+ * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
+ * @param context
+ * the context to be used
+ * @return The number of bytes successfully extracted into the provided byte[] array.
+ */
+ int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) {
+ if (context.buffer != null) {
+ final int len = Math.min(available(context), bAvail);
+ System.arraycopy(context.buffer, context.readPos, b, bPos, len);
+ context.readPos += len;
+ if (context.readPos >= context.pos) {
+ context.buffer = null; // so hasData() will return false, and this method can return -1
+ }
+ return len;
+ }
+ return context.eof ? EOF : 0;
+ }
+
+ /**
+ * Checks if a byte value is whitespace or not.
+ * Whitespace is taken to mean: space, tab, CR, LF
+ * @param byteToCheck
+ * the byte to check
+ * @return true if byte is whitespace, false otherwise
+ */
+ protected static boolean isWhiteSpace(final byte byteToCheck) {
+ switch (byteToCheck) {
+ case ' ' :
+ case '\n' :
+ case '\r' :
+ case '\t' :
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ /**
+ * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+ * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type byte[]
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof byte[])) {
+ throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]");
+ }
+ return encode((byte[]) obj);
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
+ * Uses UTF8 encoding.
+ *
+ * @param pArray
+ * a byte array containing binary data
+ * @return A String containing only Base-N character data
+ */
+ public String encodeToString(final byte[] pArray) {
+ return StringUtils.newStringUtf8(encode(pArray));
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
+ * Uses UTF8 encoding.
+ *
+ * @param pArray a byte array containing binary data
+ * @return String containing only character data in the appropriate alphabet.
+ */
+ public String encodeAsString(final byte[] pArray){
+ return StringUtils.newStringUtf8(encode(pArray));
+ }
+
+ /**
+ * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+ * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
+ *
+ * @param obj
+ * Object to decode
+ * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String
+ * supplied.
+ * @throws DecoderException
+ * if the parameter supplied is not of type byte[]
+ */
+ @Override
+ public Object decode(final Object obj) throws DecoderException {
+ if (obj instanceof byte[]) {
+ return decode((byte[]) obj);
+ } else if (obj instanceof String) {
+ return decode((String) obj);
+ } else {
+ throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String");
+ }
+ }
+
+ /**
+ * Decodes a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray
+ * A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(final String pArray) {
+ return decode(StringUtils.getBytesUtf8(pArray));
+ }
+
+ /**
+ * Decodes a byte[] containing characters in the Base-N alphabet.
+ *
+ * @param pArray
+ * A byte array containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ @Override
+ public byte[] decode(final byte[] pArray) {
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ final Context context = new Context();
+ decode(pArray, 0, pArray.length, context);
+ decode(pArray, 0, EOF, context); // Notify decoder of EOF.
+ final byte[] result = new byte[context.pos];
+ readResults(result, 0, result.length, context);
+ return result;
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+ *
+ * @param pArray
+ * a byte array containing binary data
+ * @return A byte array containing only the basen alphabetic character data
+ */
+ @Override
+ public byte[] encode(final byte[] pArray) {
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ final Context context = new Context();
+ encode(pArray, 0, pArray.length, context);
+ encode(pArray, 0, EOF, context); // Notify encoder of EOF.
+ final byte[] buf = new byte[context.pos - context.readPos];
+ readResults(buf, 0, buf.length, context);
+ return buf;
+ }
+
+ // package protected for access from I/O streams
+ abstract void encode(byte[] pArray, int i, int length, Context context);
+
+ // package protected for access from I/O streams
+ abstract void decode(byte[] pArray, int i, int length, Context context);
+
+ /**
+ * Returns whether or not the octet
is in the current alphabet.
+ * Does not allow whitespace or pad.
+ *
+ * @param value The value to test
+ *
+ * @return true
if the value is defined in the current alphabet, false
otherwise.
+ */
+ protected abstract boolean isInAlphabet(byte value);
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the alphabet.
+ * The method optionally treats whitespace and pad as valid.
+ *
+ * @param arrayOctet byte array to test
+ * @param allowWSPad if true
, then whitespace and PAD are also allowed
+ *
+ * @return true
if all bytes are valid characters in the alphabet or if the byte array is empty;
+ * false
, otherwise
+ */
+ public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (!isInAlphabet(arrayOctet[i]) &&
+ (!allowWSPad || (arrayOctet[i] != pad) && !isWhiteSpace(arrayOctet[i]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the alphabet.
+ * The method treats whitespace and PAD as valid.
+ *
+ * @param basen String to test
+ * @return true
if all characters in the String are valid characters in the alphabet or if
+ * the String is empty; false
, otherwise
+ * @see #isInAlphabet(byte[], boolean)
+ */
+ public boolean isInAlphabet(final String basen) {
+ return isInAlphabet(StringUtils.getBytesUtf8(basen), true);
+ }
+
+ /**
+ * Tests a given byte array to see if it contains any characters within the alphabet or PAD.
+ *
+ * Intended for use in checking line-ending arrays
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return true
if any byte is a valid character in the alphabet or PAD; false
otherwise
+ */
+ protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
+ if (arrayOctet == null) {
+ return false;
+ }
+ for (final byte element : arrayOctet) {
+ if (pad == element || isInAlphabet(element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Calculates the amount of space needed to encode the supplied array.
+ *
+ * @param pArray byte[] array which will later be encoded
+ *
+ * @return amount of space needed to encoded the supplied array.
+ * Returns a long since a max-len array will require > Integer.MAX_VALUE
+ */
+ public long getEncodedLength(final byte[] pArray) {
+ // Calculate non-chunked size - rounded up to allow for padding
+ // cast to long is needed to avoid possibility of overflow
+ long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize;
+ if (lineLength > 0) { // We're using chunking
+ // Round up to nearest multiple
+ len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
+ }
+ return len;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/BaseNCodecInputStream.java b/src/main/java/org/apache/commons/codec/binary/BaseNCodecInputStream.java
new file mode 100644
index 00000000..30b2cb3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/BaseNCodecInputStream.java
@@ -0,0 +1,211 @@
+/*
+ * 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.commons.codec.binary;
+
+import static org.apache.commons.codec.binary.BaseNCodec.EOF;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.codec.binary.BaseNCodec.Context;
+
+/**
+ * Abstract superclass for Base-N input streams.
+ *
+ * @since 1.5
+ * @version $Id$
+ */
+public class BaseNCodecInputStream extends FilterInputStream {
+
+ private final BaseNCodec baseNCodec;
+
+ private final boolean doEncode;
+
+ private final byte[] singleByte = new byte[1];
+
+ private final Context context = new Context();
+
+ protected BaseNCodecInputStream(final InputStream in, final BaseNCodec baseNCodec, final boolean doEncode) {
+ super(in);
+ this.doEncode = doEncode;
+ this.baseNCodec = baseNCodec;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return 0
if the {@link InputStream} has reached EOF
,
+ * 1
otherwise
+ * @since 1.7
+ */
+ @Override
+ public int available() throws IOException {
+ // Note: the logic is similar to the InflaterInputStream:
+ // as long as we have not reached EOF, indicate that there is more
+ // data available. As we do not know for sure how much data is left,
+ // just return 1 as a safe guess.
+
+ return context.eof ? 0 : 1;
+ }
+
+ /**
+ * Marks the current position in this input stream.
+ *
The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
+ *
+ * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
+ * @since 1.7
+ */
+ @Override
+ public synchronized void mark(final int readLimit) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always returns false
+ */
+ @Override
+ public boolean markSupported() {
+ return false; // not an easy job to support marks
+ }
+
+ /**
+ * Reads one byte
from this input stream.
+ *
+ * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ @Override
+ public int read() throws IOException {
+ int r = read(singleByte, 0, 1);
+ while (r == 0) {
+ r = read(singleByte, 0, 1);
+ }
+ if (r > 0) {
+ final byte b = singleByte[0];
+ return b < 0 ? 256 + b : b;
+ }
+ return EOF;
+ }
+
+ /**
+ * Attempts to read len
bytes into the specified b
array starting at offset
+ * from this InputStream.
+ *
+ * @param b
+ * destination byte array
+ * @param offset
+ * where to start writing the bytes
+ * @param len
+ * maximum number of bytes to read
+ *
+ * @return number of bytes read
+ * @throws IOException
+ * if an I/O error occurs.
+ * @throws NullPointerException
+ * if the byte array parameter is null
+ * @throws IndexOutOfBoundsException
+ * if offset, len or buffer size are invalid
+ */
+ @Override
+ public int read(final byte b[], final int offset, final int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (offset < 0 || len < 0) {
+ throw new IndexOutOfBoundsException();
+ } else if (offset > b.length || offset + len > b.length) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ } else {
+ int readLen = 0;
+ /*
+ Rationale for while-loop on (readLen == 0):
+ -----
+ Base32.readResults() usually returns > 0 or EOF (-1). In the
+ rare case where it returns 0, we just keep trying.
+
+ This is essentially an undocumented contract for InputStream
+ implementors that want their code to work properly with
+ java.io.InputStreamReader, since the latter hates it when
+ InputStream.read(byte[]) returns a zero. Unfortunately our
+ readResults() call must return 0 if a large amount of the data
+ being decoded was non-base32, so this while-loop enables proper
+ interop with InputStreamReader for that scenario.
+ -----
+ This is a fix for CODEC-101
+ */
+ while (readLen == 0) {
+ if (!baseNCodec.hasData(context)) {
+ final byte[] buf = new byte[doEncode ? 4096 : 8192];
+ final int c = in.read(buf);
+ if (doEncode) {
+ baseNCodec.encode(buf, 0, c, context);
+ } else {
+ baseNCodec.decode(buf, 0, c, context);
+ }
+ }
+ readLen = baseNCodec.readResults(b, offset, len, context);
+ }
+ return readLen;
+ }
+ }
+
+ /**
+ * Repositions this stream to the position at the time the mark method was last called on this input stream.
+ *
+ * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
+ *
+ * @throws IOException if this method is invoked
+ * @since 1.7
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("mark/reset not supported");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if the provided skip length is negative
+ * @since 1.7
+ */
+ @Override
+ public long skip(final long n) throws IOException {
+ if (n < 0) {
+ throw new IllegalArgumentException("Negative skip length: " + n);
+ }
+
+ // skip in chunks of 512 bytes
+ final byte[] b = new byte[512];
+ long todo = n;
+
+ while (todo > 0) {
+ int len = (int) Math.min(b.length, todo);
+ len = this.read(b, 0, len);
+ if (len == EOF) {
+ break;
+ }
+ todo -= len;
+ }
+
+ return n - todo;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/BaseNCodecOutputStream.java b/src/main/java/org/apache/commons/codec/binary/BaseNCodecOutputStream.java
new file mode 100644
index 00000000..90d2f535
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/BaseNCodecOutputStream.java
@@ -0,0 +1,176 @@
+/*
+ * 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.commons.codec.binary;
+
+import static org.apache.commons.codec.binary.BaseNCodec.EOF;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.commons.codec.binary.BaseNCodec.Context;
+
+/**
+ * Abstract superclass for Base-N output streams.
+ *
+ * To write the EOF marker without closing the stream, call {@link #eof()} or use an Apache Commons IO CloseShieldOutputStream .
+ *
+ *
+ * @since 1.5
+ * @version $Id$
+ */
+public class BaseNCodecOutputStream extends FilterOutputStream {
+
+ private final boolean doEncode;
+
+ private final BaseNCodec baseNCodec;
+
+ private final byte[] singleByte = new byte[1];
+
+ private final Context context = new Context();
+
+ // TODO should this be protected?
+ public BaseNCodecOutputStream(final OutputStream out, final BaseNCodec basedCodec, final boolean doEncode) {
+ super(out);
+ this.baseNCodec = basedCodec;
+ this.doEncode = doEncode;
+ }
+
+ /**
+ * Writes the specified byte
to this output stream.
+ *
+ * @param i
+ * source byte
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ @Override
+ public void write(final int i) throws IOException {
+ singleByte[0] = (byte) i;
+ write(singleByte, 0, 1);
+ }
+
+ /**
+ * Writes len
bytes from the specified b
array starting at offset
to this
+ * output stream.
+ *
+ * @param b
+ * source byte array
+ * @param offset
+ * where to start reading the bytes
+ * @param len
+ * maximum number of bytes to write
+ *
+ * @throws IOException
+ * if an I/O error occurs.
+ * @throws NullPointerException
+ * if the byte array parameter is null
+ * @throws IndexOutOfBoundsException
+ * if offset, len or buffer size are invalid
+ */
+ @Override
+ public void write(final byte b[], final int offset, final int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (offset < 0 || len < 0) {
+ throw new IndexOutOfBoundsException();
+ } else if (offset > b.length || offset + len > b.length) {
+ throw new IndexOutOfBoundsException();
+ } else if (len > 0) {
+ if (doEncode) {
+ baseNCodec.encode(b, offset, len, context);
+ } else {
+ baseNCodec.decode(b, offset, len, context);
+ }
+ flush(false);
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is
+ * true, the wrapped stream will also be flushed.
+ *
+ * @param propagate
+ * boolean flag to indicate whether the wrapped OutputStream should also be flushed.
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ private void flush(final boolean propagate) throws IOException {
+ final int avail = baseNCodec.available(context);
+ if (avail > 0) {
+ final byte[] buf = new byte[avail];
+ final int c = baseNCodec.readResults(buf, 0, avail, context);
+ if (c > 0) {
+ out.write(buf, 0, c);
+ }
+ }
+ if (propagate) {
+ out.flush();
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
+ *
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ @Override
+ public void flush() throws IOException {
+ flush(true);
+ }
+
+ /**
+ * Closes this output stream and releases any system resources associated with the stream.
+ *
+ * To write the EOF marker without closing the stream, call {@link #eof()} or use an
+ * Apache Commons IO CloseShieldOutputStream .
+ *
+ *
+ * @throws IOException
+ * if an I/O error occurs.
+ */
+ @Override
+ public void close() throws IOException {
+ eof();
+ flush();
+ out.close();
+ }
+
+ /**
+ * Writes EOF.
+ *
+ * @throws IOException
+ * if an I/O error occurs.
+ * @since 1.11
+ */
+ public void eof() throws IOException {
+ // Notify encoder of EOF (-1).
+ if (doEncode) {
+ baseNCodec.encode(singleByte, 0, EOF, context);
+ } else {
+ baseNCodec.decode(singleByte, 0, EOF, context);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/BinaryCodec.java b/src/main/java/org/apache/commons/codec/binary/BinaryCodec.java
new file mode 100644
index 00000000..54f8a943
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/BinaryCodec.java
@@ -0,0 +1,301 @@
+/*
+ * 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.commons.codec.binary;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+
+/**
+ * Converts between byte arrays and strings of "0"s and "1"s.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * TODO: may want to add more bit vector functions like and/or/xor/nand
+ * TODO: also might be good to generate boolean[] from byte[] et cetera.
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+public class BinaryCodec implements BinaryDecoder, BinaryEncoder {
+ /*
+ * tried to avoid using ArrayUtils to minimize dependencies while using these empty arrays - dep is just not worth
+ * it.
+ */
+ /** Empty char array. */
+ private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+
+ /** Empty byte array. */
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ /** Mask for bit 0 of a byte. */
+ private static final int BIT_0 = 1;
+
+ /** Mask for bit 1 of a byte. */
+ private static final int BIT_1 = 0x02;
+
+ /** Mask for bit 2 of a byte. */
+ private static final int BIT_2 = 0x04;
+
+ /** Mask for bit 3 of a byte. */
+ private static final int BIT_3 = 0x08;
+
+ /** Mask for bit 4 of a byte. */
+ private static final int BIT_4 = 0x10;
+
+ /** Mask for bit 5 of a byte. */
+ private static final int BIT_5 = 0x20;
+
+ /** Mask for bit 6 of a byte. */
+ private static final int BIT_6 = 0x40;
+
+ /** Mask for bit 7 of a byte. */
+ private static final int BIT_7 = 0x80;
+
+ private static final int[] BITS = {BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7};
+
+ /**
+ * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
+ *
+ * @param raw
+ * the raw binary data to convert
+ * @return 0 and 1 ASCII character bytes one for each bit of the argument
+ * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
+ */
+ @Override
+ public byte[] encode(final byte[] raw) {
+ return toAsciiBytes(raw);
+ }
+
+ /**
+ * Converts an array of raw binary data into an array of ASCII 0 and 1 chars.
+ *
+ * @param raw
+ * the raw binary data to convert
+ * @return 0 and 1 ASCII character chars one for each bit of the argument
+ * @throws EncoderException
+ * if the argument is not a byte[]
+ * @see org.apache.commons.codec.Encoder#encode(Object)
+ */
+ @Override
+ public Object encode(final Object raw) throws EncoderException {
+ if (!(raw instanceof byte[])) {
+ throw new EncoderException("argument not a byte array");
+ }
+ return toAsciiChars((byte[]) raw);
+ }
+
+ /**
+ * Decodes a byte array where each byte represents an ASCII '0' or '1'.
+ *
+ * @param ascii
+ * each byte represents an ASCII '0' or '1'
+ * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
+ * @throws DecoderException
+ * if argument is not a byte[], char[] or String
+ * @see org.apache.commons.codec.Decoder#decode(Object)
+ */
+ @Override
+ public Object decode(final Object ascii) throws DecoderException {
+ if (ascii == null) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ if (ascii instanceof byte[]) {
+ return fromAscii((byte[]) ascii);
+ }
+ if (ascii instanceof char[]) {
+ return fromAscii((char[]) ascii);
+ }
+ if (ascii instanceof String) {
+ return fromAscii(((String) ascii).toCharArray());
+ }
+ throw new DecoderException("argument not a byte array");
+ }
+
+ /**
+ * Decodes a byte array where each byte represents an ASCII '0' or '1'.
+ *
+ * @param ascii
+ * each byte represents an ASCII '0' or '1'
+ * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
+ * @see org.apache.commons.codec.Decoder#decode(Object)
+ */
+ @Override
+ public byte[] decode(final byte[] ascii) {
+ return fromAscii(ascii);
+ }
+
+ /**
+ * Decodes a String where each char of the String represents an ASCII '0' or '1'.
+ *
+ * @param ascii
+ * String of '0' and '1' characters
+ * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
+ * @see org.apache.commons.codec.Decoder#decode(Object)
+ */
+ public byte[] toByteArray(final String ascii) {
+ if (ascii == null) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ return fromAscii(ascii.toCharArray());
+ }
+
+ // ------------------------------------------------------------------------
+ //
+ // static codec operations
+ //
+ // ------------------------------------------------------------------------
+ /**
+ * Decodes a char array where each char represents an ASCII '0' or '1'.
+ *
+ * @param ascii
+ * each char represents an ASCII '0' or '1'
+ * @return the raw encoded binary where each bit corresponds to a char in the char array argument
+ */
+ public static byte[] fromAscii(final char[] ascii) {
+ if (ascii == null || ascii.length == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ // get length/8 times bytes with 3 bit shifts to the right of the length
+ final byte[] l_raw = new byte[ascii.length >> 3];
+ /*
+ * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
+ * loop.
+ */
+ for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) {
+ for (int bits = 0; bits < BITS.length; ++bits) {
+ if (ascii[jj - bits] == '1') {
+ l_raw[ii] |= BITS[bits];
+ }
+ }
+ }
+ return l_raw;
+ }
+
+ /**
+ * Decodes a byte array where each byte represents an ASCII '0' or '1'.
+ *
+ * @param ascii
+ * each byte represents an ASCII '0' or '1'
+ * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
+ */
+ public static byte[] fromAscii(final byte[] ascii) {
+ if (isEmpty(ascii)) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ // get length/8 times bytes with 3 bit shifts to the right of the length
+ final byte[] l_raw = new byte[ascii.length >> 3];
+ /*
+ * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
+ * loop.
+ */
+ for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) {
+ for (int bits = 0; bits < BITS.length; ++bits) {
+ if (ascii[jj - bits] == '1') {
+ l_raw[ii] |= BITS[bits];
+ }
+ }
+ }
+ return l_raw;
+ }
+
+ /**
+ * Returns true
if the given array is null
or empty (size 0.)
+ *
+ * @param array
+ * the source array
+ * @return true
if the given array is null
or empty (size 0.)
+ */
+ private static boolean isEmpty(final byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts an array of raw binary data into an array of ASCII 0 and 1 character bytes - each byte is a truncated
+ * char.
+ *
+ * @param raw
+ * the raw binary data to convert
+ * @return an array of 0 and 1 character bytes for each bit of the argument
+ * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
+ */
+ public static byte[] toAsciiBytes(final byte[] raw) {
+ if (isEmpty(raw)) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ // get 8 times the bytes with 3 bit shifts to the left of the length
+ final byte[] l_ascii = new byte[raw.length << 3];
+ /*
+ * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
+ * loop.
+ */
+ for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) {
+ for (int bits = 0; bits < BITS.length; ++bits) {
+ if ((raw[ii] & BITS[bits]) == 0) {
+ l_ascii[jj - bits] = '0';
+ } else {
+ l_ascii[jj - bits] = '1';
+ }
+ }
+ }
+ return l_ascii;
+ }
+
+ /**
+ * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
+ *
+ * @param raw
+ * the raw binary data to convert
+ * @return an array of 0 and 1 characters for each bit of the argument
+ * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
+ */
+ public static char[] toAsciiChars(final byte[] raw) {
+ if (isEmpty(raw)) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ // get 8 times the bytes with 3 bit shifts to the left of the length
+ final char[] l_ascii = new char[raw.length << 3];
+ /*
+ * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
+ * loop.
+ */
+ for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) {
+ for (int bits = 0; bits < BITS.length; ++bits) {
+ if ((raw[ii] & BITS[bits]) == 0) {
+ l_ascii[jj - bits] = '0';
+ } else {
+ l_ascii[jj - bits] = '1';
+ }
+ }
+ }
+ return l_ascii;
+ }
+
+ /**
+ * Converts an array of raw binary data into a String of ASCII 0 and 1 characters.
+ *
+ * @param raw
+ * the raw binary data to convert
+ * @return a String of 0 and 1 characters representing the binary data
+ * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
+ */
+ public static String toAsciiString(final byte[] raw) {
+ return new String(toAsciiChars(raw));
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/CharSequenceUtils.java b/src/main/java/org/apache/commons/codec/binary/CharSequenceUtils.java
new file mode 100644
index 00000000..b886a826
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/CharSequenceUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.commons.codec.binary;
+
+/**
+ *
+ * Operations on {@link CharSequence} that are null
safe.
+ *
+ *
+ * Copied from Apache Commons Lang r1586295 on April 10, 2014 (day of 3.3.2 release).
+ *
+ *
+ * @see CharSequence
+ * @since 1.10
+ */
+public class CharSequenceUtils {
+
+ /**
+ * Green implementation of regionMatches.
+ *
+ * @param cs
+ * the CharSequence
to be processed
+ * @param ignoreCase
+ * whether or not to be case insensitive
+ * @param thisStart
+ * the index to start on the cs
CharSequence
+ * @param substring
+ * the CharSequence
to be looked for
+ * @param start
+ * the index to start on the substring
CharSequence
+ * @param length
+ * character length of the region
+ * @return whether the region matched
+ */
+ static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
+ final CharSequence substring, final int start, final int length) {
+ if (cs instanceof String && substring instanceof String) {
+ return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
+ }
+ int index1 = thisStart;
+ int index2 = start;
+ int tmpLen = length;
+
+ while (tmpLen-- > 0) {
+ char c1 = cs.charAt(index1++);
+ char c2 = substring.charAt(index2++);
+
+ if (c1 == c2) {
+ continue;
+ }
+
+ if (!ignoreCase) {
+ return false;
+ }
+
+ // The same check as in String.regionMatches():
+ if (Character.toUpperCase(c1) != Character.toUpperCase(c2) &&
+ Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/Hex.java b/src/main/java/org/apache/commons/codec/binary/Hex.java
new file mode 100644
index 00000000..51857fe0
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/Hex.java
@@ -0,0 +1,443 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.CharEncoding;
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+
+/**
+ * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in
+ * {@link #DEFAULT_CHARSET_NAME}
+ *
+ * This class is thread-safe.
+ *
+ * @since 1.1
+ * @version $Id$
+ */
+public class Hex implements BinaryEncoder, BinaryDecoder {
+
+ /**
+ * Default charset name is {@link Charsets#UTF_8}
+ *
+ * @since 1.7
+ */
+ public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
+
+ /**
+ * Default charset name is {@link CharEncoding#UTF_8}
+ *
+ * @since 1.4
+ */
+ public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
+
+ /**
+ * Used to build output as Hex
+ */
+ private static final char[] DIGITS_LOWER =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * Used to build output as Hex
+ */
+ private static final char[] DIGITS_UPPER =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /**
+ * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
+ * returned array will be half the length of the passed array, as it takes two characters to represent any given
+ * byte. An exception is thrown if the passed char array has an odd number of elements.
+ *
+ * @param data
+ * An array of characters containing hexadecimal digits
+ * @return A byte array containing binary data decoded from the supplied char array.
+ * @throws DecoderException
+ * Thrown if an odd number or illegal of characters is supplied
+ */
+ public static byte[] decodeHex(final char[] data) throws DecoderException {
+
+ final int len = data.length;
+
+ if ((len & 0x01) != 0) {
+ throw new DecoderException("Odd number of characters.");
+ }
+
+ final byte[] out = new byte[len >> 1];
+
+ // two characters form the hex value.
+ for (int i = 0, j = 0; j < len; i++) {
+ int f = toDigit(data[j], j) << 4;
+ j++;
+ f = f | toDigit(data[j], j);
+ j++;
+ out[i] = (byte) (f & 0xFF);
+ }
+
+ return out;
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte[] to convert to Hex characters
+ * @return A char[] containing hexadecimal characters
+ */
+ public static char[] encodeHex(final byte[] data) {
+ return encodeHex(data, true);
+ }
+
+ /**
+ * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte buffer to convert to Hex characters
+ * @return A char[] containing hexadecimal characters
+ * @since 1.11
+ */
+ public static char[] encodeHex(final ByteBuffer data) {
+ return encodeHex(data, true);
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte[] to convert to Hex characters
+ * @param toLowerCase
+ * true
converts to lowercase, false
to uppercase
+ * @return A char[] containing hexadecimal characters
+ * @since 1.4
+ */
+ public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
+ return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ /**
+ * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte buffer to convert to Hex characters
+ * @param toLowerCase
+ * true
converts to lowercase, false
to uppercase
+ * @return A char[] containing hexadecimal characters
+ * @since 1.11
+ */
+ public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) {
+ return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte[] to convert to Hex characters
+ * @param toDigits
+ * the output alphabet
+ * @return A char[] containing hexadecimal characters
+ * @since 1.4
+ */
+ protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
+ final int l = data.length;
+ final char[] out = new char[l << 1];
+ // two characters form the hex value.
+ for (int i = 0, j = 0; i < l; i++) {
+ out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
+ out[j++] = toDigits[0x0F & data[i]];
+ }
+ return out;
+ }
+
+ /**
+ * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
+ * The returned array will be double the length of the passed array, as it takes two characters to represent any
+ * given byte.
+ *
+ * @param data
+ * a byte buffer to convert to Hex characters
+ * @param toDigits
+ * the output alphabet
+ * @return A char[] containing hexadecimal characters
+ * @since 1.11
+ */
+ protected static char[] encodeHex(final ByteBuffer data, final char[] toDigits) {
+ return encodeHex(data.array(), toDigits);
+ }
+
+ /**
+ * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
+ * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+ *
+ * @param data
+ * a byte[] to convert to Hex characters
+ * @return A String containing hexadecimal characters
+ * @since 1.4
+ */
+ public static String encodeHexString(final byte[] data) {
+ return new String(encodeHex(data));
+ }
+
+ /**
+ * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
+ * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+ *
+ * @param data
+ * a byte buffer to convert to Hex characters
+ * @return A String containing hexadecimal characters
+ * @since 1.11
+ */
+ public static String encodeHexString(final ByteBuffer data) {
+ return new String(encodeHex(data));
+ }
+
+ /**
+ * Converts a hexadecimal character to an integer.
+ *
+ * @param ch
+ * A character to convert to an integer digit
+ * @param index
+ * The index of the character in the source
+ * @return An integer
+ * @throws DecoderException
+ * Thrown if ch is an illegal hex character
+ */
+ protected static int toDigit(final char ch, final int index) throws DecoderException {
+ final int digit = Character.digit(ch, 16);
+ if (digit == -1) {
+ throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index);
+ }
+ return digit;
+ }
+
+ private final Charset charset;
+
+ /**
+ * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET}
+ */
+ public Hex() {
+ // use default encoding
+ this.charset = DEFAULT_CHARSET;
+ }
+
+ /**
+ * Creates a new codec with the given Charset.
+ *
+ * @param charset
+ * the charset.
+ * @since 1.7
+ */
+ public Hex(final Charset charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * Creates a new codec with the given charset name.
+ *
+ * @param charsetName
+ * the charset name.
+ * @throws java.nio.charset.UnsupportedCharsetException
+ * If the named charset is unavailable
+ * @since 1.4
+ * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
+ */
+ public Hex(final String charsetName) {
+ this(Charset.forName(charsetName));
+ }
+
+ /**
+ * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
+ * The returned array will be half the length of the passed array, as it takes two characters to represent any given
+ * byte. An exception is thrown if the passed char array has an odd number of elements.
+ *
+ * @param array
+ * An array of character bytes containing hexadecimal digits
+ * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+ * @throws DecoderException
+ * Thrown if an odd number of characters is supplied to this function
+ * @see #decodeHex(char[])
+ */
+ @Override
+ public byte[] decode(final byte[] array) throws DecoderException {
+ return decodeHex(new String(array, getCharset()).toCharArray());
+ }
+
+ /**
+ * Converts a buffer of character bytes representing hexadecimal values into an array of bytes of those same values.
+ * The returned array will be half the length of the passed array, as it takes two characters to represent any given
+ * byte. An exception is thrown if the passed char array has an odd number of elements.
+ *
+ * @param buffer
+ * An array of character bytes containing hexadecimal digits
+ * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+ * @throws DecoderException
+ * Thrown if an odd number of characters is supplied to this function
+ * @see #decodeHex(char[])
+ * @since 1.11
+ */
+ public byte[] decode(final ByteBuffer buffer) throws DecoderException {
+ return decodeHex(new String(buffer.array(), getCharset()).toCharArray());
+ }
+
+ /**
+ * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
+ * same values. The returned array will be half the length of the passed String or array, as it takes two characters
+ * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
+ *
+ * @param object
+ * A String, ByteBuffer, byte[], or an array of character bytes containing hexadecimal digits
+ * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+ * @throws DecoderException
+ * Thrown if an odd number of characters is supplied to this function or the object is not a String or
+ * char[]
+ * @see #decodeHex(char[])
+ */
+ @Override
+ public Object decode(final Object object) throws DecoderException {
+ if (object instanceof String) {
+ return decode(((String) object).toCharArray());
+ } else if (object instanceof byte[]) {
+ return decode((byte[]) object);
+ } else if (object instanceof ByteBuffer) {
+ return decode((ByteBuffer) object);
+ } else {
+ try {
+ return decodeHex((char[]) object);
+ } catch (final ClassCastException e) {
+ throw new DecoderException(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
+ * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
+ * represent any given byte.
+ *
+ * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
+ * {@link #getCharset()}.
+ *
+ *
+ * @param array
+ * a byte[] to convert to Hex characters
+ * @return A byte[] containing the bytes of the hexadecimal characters
+ * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid.
+ * @see #encodeHex(byte[])
+ */
+ @Override
+ public byte[] encode(final byte[] array) {
+ return encodeHexString(array).getBytes(this.getCharset());
+ }
+
+ /**
+ * Converts byte buffer into an array of bytes for the characters representing the hexadecimal values of each
+ * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
+ * represent any given byte.
+ *
+ * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
+ * {@link #getCharset()}.
+ *
+ *
+ * @param array
+ * a byte buffer to convert to Hex characters
+ * @return A byte[] containing the bytes of the hexadecimal characters
+ * @see #encodeHex(byte[])
+ * @since 1.11
+ */
+ public byte[] encode(final ByteBuffer array) {
+ return encodeHexString(array).getBytes(this.getCharset());
+ }
+
+ /**
+ * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
+ * byte in order. The returned array will be double the length of the passed String or array, as it takes two
+ * characters to represent any given byte.
+ *
+ * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
+ * {@link #getCharset()}.
+ *
+ *
+ * @param object
+ * a String, ByteBuffer, or byte[] to convert to Hex characters
+ * @return A char[] containing hexadecimal characters
+ * @throws EncoderException
+ * Thrown if the given object is not a String or byte[]
+ * @see #encodeHex(byte[])
+ */
+ @Override
+ public Object encode(final Object object) throws EncoderException {
+ byte[] byteArray;
+ if (object instanceof String) {
+ byteArray = ((String) object).getBytes(this.getCharset());
+ } else if (object instanceof ByteBuffer) {
+ byteArray = ((ByteBuffer) object).array();
+ } else {
+ try {
+ byteArray = (byte[]) object;
+ } catch (final ClassCastException e) {
+ throw new EncoderException(e.getMessage(), e);
+ }
+ }
+ return encodeHex(byteArray);
+ }
+
+ /**
+ * Gets the charset.
+ *
+ * @return the charset.
+ * @since 1.7
+ */
+ public Charset getCharset() {
+ return this.charset;
+ }
+
+ /**
+ * Gets the charset name.
+ *
+ * @return the charset name.
+ * @since 1.4
+ */
+ public String getCharsetName() {
+ return this.charset.name();
+ }
+
+ /**
+ * Returns a string representation of the object, which includes the charset name.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return super.toString() + "[charsetName=" + this.charset + "]";
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/StringUtils.java b/src/main/java/org/apache/commons/codec/binary/StringUtils.java
new file mode 100644
index 00000000..84a2a727
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/StringUtils.java
@@ -0,0 +1,422 @@
+/*
+ * 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.commons.codec.binary;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import org.apache.commons.codec.CharEncoding;
+import org.apache.commons.codec.Charsets;
+
+/**
+ * Converts String to and from bytes using the encodings required by the Java specification. These encodings are
+ * specified in
+ * Standard charsets .
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see CharEncoding
+ * @see Standard charsets
+ * @version $Id$
+ * @since 1.4
+ */
+public class StringUtils {
+
+ /**
+ *
+ * Compares two CharSequences, returning true
if they represent equal sequences of characters.
+ *
+ *
+ *
+ * null
s are handled without exceptions. Two null
references are considered to be equal.
+ * The comparison is case sensitive.
+ *
+ *
+ *
+ * StringUtils.equals(null, null) = true
+ * StringUtils.equals(null, "abc") = false
+ * StringUtils.equals("abc", null) = false
+ * StringUtils.equals("abc", "abc") = true
+ * StringUtils.equals("abc", "ABC") = false
+ *
+ *
+ *
+ * Copied from Apache Commons Lang r1583482 on April 10, 2014 (day of 3.3.2 release).
+ *
+ *
+ * @see Object#equals(Object)
+ * @param cs1
+ * the first CharSequence, may be null
+ * @param cs2
+ * the second CharSequence, may be null
+ * @return true
if the CharSequences are equal (case-sensitive), or both null
+ * @since 1.10
+ */
+ public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1 instanceof String && cs2 instanceof String) {
+ return cs1.equals(cs2);
+ }
+ return CharSequenceUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length()));
+ }
+
+ /**
+ * Calls {@link String#getBytes(Charset)}
+ *
+ * @param string
+ * The string to encode (if null, return null).
+ * @param charset
+ * The {@link Charset} to encode the String
+ * @return the encoded bytes
+ */
+ private static byte[] getBytes(final String string, final Charset charset) {
+ if (string == null) {
+ return null;
+ }
+ return string.getBytes(charset);
+ }
+
+ /**
+ * Calls {@link String#getBytes(Charset)}
+ *
+ * @param string
+ * The string to encode (if null, return null).
+ * @param charset
+ * The {@link Charset} to encode the String
+ * @return the encoded bytes
+ * @since 1.11
+ */
+ private static ByteBuffer getByteBuffer(final String string, final Charset charset) {
+ if (string == null) {
+ return null;
+ }
+ return ByteBuffer.wrap(string.getBytes(charset));
+ }
+
+ /**
+ * Encodes the given string into a byte buffer using the UTF-8 charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ * @since 1.11
+ */
+ public static ByteBuffer getByteBufferUtf8(final String string) {
+ return getByteBuffer(string, Charsets.UTF_8);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the ISO-8859-1 charset, storing the result into a new
+ * byte array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesIso8859_1(final String string) {
+ return getBytes(string, Charsets.ISO_8859_1);
+ }
+
+
+ /**
+ * Encodes the given string into a sequence of bytes using the named charset, storing the result into a new byte
+ * array.
+ *
+ * This method catches {@link UnsupportedEncodingException} and rethrows it as {@link IllegalStateException}, which
+ * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE.
+ *
+ *
+ * @param string
+ * the String to encode, may be null
+ * @param charsetName
+ * The name of a required {@link java.nio.charset.Charset}
+ * @return encoded bytes, or null
if the input string was null
+ * @throws IllegalStateException
+ * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a
+ * required charset name.
+ * @see CharEncoding
+ * @see String#getBytes(String)
+ */
+ public static byte[] getBytesUnchecked(final String string, final String charsetName) {
+ if (string == null) {
+ return null;
+ }
+ try {
+ return string.getBytes(charsetName);
+ } catch (final UnsupportedEncodingException e) {
+ throw StringUtils.newIllegalStateException(charsetName, e);
+ }
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the US-ASCII charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUsAscii(final String string) {
+ return getBytes(string, Charsets.US_ASCII);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-16 charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUtf16(final String string) {
+ return getBytes(string, Charsets.UTF_16);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-16BE charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUtf16Be(final String string) {
+ return getBytes(string, Charsets.UTF_16BE);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-16LE charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUtf16Le(final String string) {
+ return getBytes(string, Charsets.UTF_16LE);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be null
+ * @return encoded bytes, or null
if the input string was null
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Standard charsets
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUtf8(final String string) {
+ return getBytes(string, Charsets.UTF_8);
+ }
+
+ private static IllegalStateException newIllegalStateException(final String charsetName,
+ final UnsupportedEncodingException e) {
+ return new IllegalStateException(charsetName + ": " + e);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the given charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @param charset
+ * The {@link Charset} to encode the String
+ * @return A new String
decoded from the specified array of bytes using the given charset,
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ */
+ private static String newString(final byte[] bytes, final Charset charset) {
+ return bytes == null ? null : new String(bytes, charset);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the given charset.
+ *
+ * This method catches {@link UnsupportedEncodingException} and re-throws it as {@link IllegalStateException}, which
+ * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE.
+ *
+ *
+ * @param bytes
+ * The bytes to be decoded into characters, may be null
+ * @param charsetName
+ * The name of a required {@link java.nio.charset.Charset}
+ * @return A new String
decoded from the specified array of bytes using the given charset,
+ * or null
if the input byte array was null
.
+ * @throws IllegalStateException
+ * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a
+ * required charset name.
+ * @see CharEncoding
+ * @see String#String(byte[], String)
+ */
+ public static String newString(final byte[] bytes, final String charsetName) {
+ if (bytes == null) {
+ return null;
+ }
+ try {
+ return new String(bytes, charsetName);
+ } catch (final UnsupportedEncodingException e) {
+ throw StringUtils.newIllegalStateException(charsetName, e);
+ }
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the ISO-8859-1 charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters, may be null
+ * @return A new String
decoded from the specified array of bytes using the ISO-8859-1 charset, or
+ * null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringIso8859_1(final byte[] bytes) {
+ return new String(bytes, Charsets.ISO_8859_1);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the US-ASCII charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new String
decoded from the specified array of bytes using the US-ASCII charset,
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringUsAscii(final byte[] bytes) {
+ return new String(bytes, Charsets.US_ASCII);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the UTF-16 charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new String
decoded from the specified array of bytes using the UTF-16 charset
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringUtf16(final byte[] bytes) {
+ return new String(bytes, Charsets.UTF_16);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the UTF-16BE charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new String
decoded from the specified array of bytes using the UTF-16BE charset,
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringUtf16Be(final byte[] bytes) {
+ return new String(bytes, Charsets.UTF_16BE);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the UTF-16LE charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new String
decoded from the specified array of bytes using the UTF-16LE charset,
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringUtf16Le(final byte[] bytes) {
+ return new String(bytes, Charsets.UTF_16LE);
+ }
+
+ /**
+ * Constructs a new String
by decoding the specified array of bytes using the UTF-8 charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new String
decoded from the specified array of bytes using the UTF-8 charset,
+ * or null
if the input byte array was null
.
+ * @throws NullPointerException
+ * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is
+ * required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ */
+ public static String newStringUtf8(final byte[] bytes) {
+ return newString(bytes, Charsets.UTF_8);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/binary/package.html b/src/main/java/org/apache/commons/codec/binary/package.html
new file mode 100644
index 00000000..13345ece
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/binary/package.html
@@ -0,0 +1,21 @@
+
+
+
+ Base64, Base32, Binary, and Hexadecimal String encoding and decoding.
+
+
diff --git a/src/main/java/org/apache/commons/codec/digest/B64.java b/src/main/java/org/apache/commons/codec/digest/B64.java
new file mode 100644
index 00000000..93523e84
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/B64.java
@@ -0,0 +1,79 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.util.Random;
+
+/**
+ * Base64 like method to convert binary bytes into ASCII chars.
+ *
+ * TODO: Can Base64 be reused?
+ *
+ *
+ * This class is immutable and thread-safe.
+ *
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+class B64 {
+
+ /**
+ * Table with characters for Base64 transformation.
+ */
+ static final String B64T = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ /**
+ * Base64 like conversion of bytes to ASCII chars.
+ *
+ * @param b2
+ * A byte from the result.
+ * @param b1
+ * A byte from the result.
+ * @param b0
+ * A byte from the result.
+ * @param outLen
+ * The number of expected output chars.
+ * @param buffer
+ * Where the output chars is appended to.
+ */
+ static void b64from24bit(final byte b2, final byte b1, final byte b0, final int outLen,
+ final StringBuilder buffer) {
+ // The bit masking is necessary because the JVM byte type is signed!
+ int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff);
+ // It's effectively a "for" loop but kept to resemble the original C code.
+ int n = outLen;
+ while (n-- > 0) {
+ buffer.append(B64T.charAt(w & 0x3f));
+ w >>= 6;
+ }
+ }
+
+ /**
+ * Generates a string of random chars from the B64T set.
+ *
+ * @param num
+ * Number of chars to generate.
+ */
+ static String getRandomSalt(final int num) {
+ final StringBuilder saltString = new StringBuilder();
+ for (int i = 1; i <= num; i++) {
+ saltString.append(B64T.charAt(new Random().nextInt(B64T.length())));
+ }
+ return saltString.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/Crypt.java b/src/main/java/org/apache/commons/codec/digest/Crypt.java
new file mode 100644
index 00000000..cd0bce69
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/Crypt.java
@@ -0,0 +1,151 @@
+/*
+ * 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.commons.codec.digest;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * GNU libc crypt(3) compatible hash method.
+ *
+ * See {@link #crypt(String, String)} for further details.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+public class Crypt {
+
+ /**
+ * Encrypts a password in a crypt(3) compatible way.
+ *
+ * A random salt and the default algorithm (currently SHA-512) are used. See {@link #crypt(String, String)} for
+ * details.
+ *
+ * @param keyBytes
+ * plaintext password
+ * @return hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String crypt(final byte[] keyBytes) {
+ return crypt(keyBytes, null);
+ }
+
+ /**
+ * Encrypts a password in a crypt(3) compatible way.
+ *
+ * If no salt is provided, a random salt and the default algorithm (currently SHA-512) will be used. See
+ * {@link #crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext password
+ * @param salt
+ * salt value
+ * @return hash value
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String crypt(final byte[] keyBytes, final String salt) {
+ if (salt == null) {
+ return Sha2Crypt.sha512Crypt(keyBytes);
+ } else if (salt.startsWith(Sha2Crypt.SHA512_PREFIX)) {
+ return Sha2Crypt.sha512Crypt(keyBytes, salt);
+ } else if (salt.startsWith(Sha2Crypt.SHA256_PREFIX)) {
+ return Sha2Crypt.sha256Crypt(keyBytes, salt);
+ } else if (salt.startsWith(Md5Crypt.MD5_PREFIX)) {
+ return Md5Crypt.md5Crypt(keyBytes, salt);
+ } else {
+ return UnixCrypt.crypt(keyBytes, salt);
+ }
+ }
+
+ /**
+ * Calculates the digest using the strongest crypt(3) algorithm.
+ *
+ * A random salt and the default algorithm (currently SHA-512) are used.
+ *
+ * @see #crypt(String, String)
+ * @param key
+ * plaintext password
+ * @return hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String crypt(final String key) {
+ return crypt(key, null);
+ }
+
+ /**
+ * Encrypts a password in a crypt(3) compatible way.
+ *
+ * The exact algorithm depends on the format of the salt string:
+ *
+ * SHA-512 salts start with {@code $6$} and are up to 16 chars long.
+ * SHA-256 salts start with {@code $5$} and are up to 16 chars long
+ * MD5 salts start with {@code $1$} and are up to 8 chars long
+ * DES, the traditional UnixCrypt algorithm is used with only 2 chars
+ * Only the first 8 chars of the passwords are used in the DES algorithm!
+ *
+ * The magic strings {@code "$apr1$"} and {@code "$2a$"} are not recognized by this method as its output should be
+ * identical with that of the libc implementation.
+ *
+ * The rest of the salt string is drawn from the set {@code [a-zA-Z0-9./]} and is cut at the maximum length of if a
+ * {@code "$"} sign is encountered. It is therefore valid to enter a complete hash value as salt to e.g. verify a
+ * password with:
+ *
+ *
+ * storedPwd.equals(crypt(enteredPwd, storedPwd))
+ *
+ *
+ * The resulting string starts with the marker string ({@code $6$}), continues with the salt value and ends with a
+ * {@code "$"} sign followed by the actual hash value. For DES the string only contains the salt and actual hash.
+ * It's total length is dependent on the algorithm used:
+ *
+ * SHA-512: 106 chars
+ * SHA-256: 63 chars
+ * MD5: 34 chars
+ * DES: 13 chars
+ *
+ *
+ * Example:
+ *
+ *
+ * crypt("secret", "$1$xxxx") => "$1$xxxx$aMkevjfEIpa35Bh3G4bAc."
+ * crypt("secret", "xx") => "xxWAum7tHdIUw"
+ *
+ *
+ * This method comes in a variation that accepts a byte[] array to support input strings that are not encoded in
+ * UTF-8 but e.g. in ISO-8859-1 where equal characters result in different byte values.
+ *
+ * @see "The man page of the libc crypt (3) function."
+ * @param key
+ * plaintext password as entered by the used
+ * @param salt
+ * salt value
+ * @return hash value, i.e. encrypted password including the salt string
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught. *
+ */
+ public static String crypt(final String key, final String salt) {
+ return crypt(key.getBytes(Charsets.UTF_8), salt);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/DigestUtils.java b/src/main/java/org/apache/commons/codec/digest/DigestUtils.java
new file mode 100644
index 00000000..f5dc9a35
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/DigestUtils.java
@@ -0,0 +1,1140 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Operations to simplify common {@link java.security.MessageDigest} tasks.
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ */
+public class DigestUtils {
+
+ private static final int STREAM_BUFFER_LENGTH = 1024;
+
+ /**
+ * Read through an ByteBuffer and returns the digest for the data
+ *
+ * @param digest
+ * The MessageDigest to use (e.g. MD5)
+ * @param data
+ * Data to digest
+ * @return the digest
+ * @throws IOException
+ * On error reading from the stream
+ */
+ private static byte[] digest(final MessageDigest messageDigest, final ByteBuffer data) {
+ messageDigest.update(data);
+ return messageDigest.digest();
+ }
+
+ /**
+ * Read through an InputStream and returns the digest for the data
+ *
+ * @param digest
+ * The MessageDigest to use (e.g. MD5)
+ * @param data
+ * Data to digest
+ * @return the digest
+ * @throws IOException
+ * On error reading from the stream
+ */
+ private static byte[] digest(final MessageDigest digest, final InputStream data) throws IOException {
+ return updateDigest(digest, data).digest();
+ }
+
+ /**
+ * Returns a MessageDigest
for the given algorithm
.
+ *
+ * @param algorithm
+ * the name of the algorithm requested. See Appendix A in the Java Cryptography Architecture Reference Guide for information about standard
+ * algorithm names.
+ * @return A digest instance.
+ * @see MessageDigest#getInstance(String)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught.
+ */
+ public static MessageDigest getDigest(final String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm);
+ } catch (final NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns an MD2 MessageDigest.
+ *
+ * @return An MD2 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD2 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#MD2
+ * @since 1.7
+ */
+ public static MessageDigest getMd2Digest() {
+ return getDigest(MessageDigestAlgorithms.MD2);
+ }
+
+ /**
+ * Returns an MD5 MessageDigest.
+ *
+ * @return An MD5 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because MD5 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#MD5
+ */
+ public static MessageDigest getMd5Digest() {
+ return getDigest(MessageDigestAlgorithms.MD5);
+ }
+
+ /**
+ * Returns an SHA-1 digest.
+ *
+ * @return An SHA-1 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-1 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#SHA_1
+ * @since 1.7
+ */
+ public static MessageDigest getSha1Digest() {
+ return getDigest(MessageDigestAlgorithms.SHA_1);
+ }
+
+ /**
+ * Returns an SHA-224 digest.
+ *
+ * Java 8 only.
+ *
+ *
+ * @return An SHA-224 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught on Java 7 and older, SHA-224 is a built-in
+ * algorithm on Java 8
+ * @see MessageDigestAlgorithms#SHA_224
+ */
+ public static MessageDigest getSha224Digest() {
+ return getDigest(MessageDigestAlgorithms.SHA_224);
+ }
+
+ /**
+ * Returns an SHA-256 digest.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @return An SHA-256 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-256 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#SHA_256
+ */
+ public static MessageDigest getSha256Digest() {
+ return getDigest(MessageDigestAlgorithms.SHA_256);
+ }
+
+ /**
+ * Returns an SHA-384 digest.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @return An SHA-384 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-384 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#SHA_384
+ */
+ public static MessageDigest getSha384Digest() {
+ return getDigest(MessageDigestAlgorithms.SHA_384);
+ }
+
+ /**
+ * Returns an SHA-512 digest.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @return An SHA-512 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught, which should never happen because SHA-512 is a
+ * built-in algorithm
+ * @see MessageDigestAlgorithms#SHA_512
+ */
+ public static MessageDigest getSha512Digest() {
+ return getDigest(MessageDigestAlgorithms.SHA_512);
+ }
+
+ /**
+ * Returns an SHA-1 digest.
+ *
+ * @return An SHA-1 digest instance.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught
+ * @deprecated Use {@link #getSha1Digest()}
+ */
+ @Deprecated
+ public static MessageDigest getShaDigest() {
+ return getSha1Digest();
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest
+ * @since 1.7
+ */
+ public static byte[] md2(final byte[] data) {
+ return getMd2Digest().digest(data);
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest
+ * @since 1.11
+ */
+ public static byte[] md2(final ByteBuffer data) {
+ return digest(getMd2Digest(), data);
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.7
+ */
+ public static byte[] md2(final InputStream data) throws IOException {
+ return digest(getMd2Digest(), data);
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return MD2 digest
+ * @since 1.7
+ */
+ public static byte[] md2(final String data) {
+ return md2(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest as a hex string
+ * @since 1.7
+ */
+ public static String md2Hex(final byte[] data) {
+ return Hex.encodeHexString(md2(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest as a hex string
+ * @since 1.11
+ */
+ public static String md2Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(md2(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.7
+ */
+ public static String md2Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(md2(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD2 digest as a hex string
+ * @since 1.7
+ */
+ public static String md2Hex(final String data) {
+ return Hex.encodeHexString(md2(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest
+ */
+ public static byte[] md5(final byte[] data) {
+ return getMd5Digest().digest(data);
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest
+ * @since 1.11
+ */
+ public static byte[] md5(final ByteBuffer data) {
+ return digest(getMd5Digest(), data);
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static byte[] md5(final InputStream data) throws IOException {
+ return digest(getMd5Digest(), data);
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 16 element byte[]
.
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return MD5 digest
+ */
+ public static byte[] md5(final String data) {
+ return md5(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest as a hex string
+ */
+ public static String md5Hex(final byte[] data) {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest as a hex string
+ * @since 1.11
+ */
+ public static String md5Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static String md5Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return MD5 digest as a hex string
+ */
+ public static String md5Hex(final String data) {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @deprecated Use {@link #sha1(byte[])}
+ */
+ @Deprecated
+ public static byte[] sha(final byte[] data) {
+ return sha1(data);
+ }
+
+/**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ * @deprecated Use {@link #sha1(InputStream)}
+ */
+@Deprecated
+public static byte[] sha(final InputStream data) throws IOException {
+ return sha1(data);
+}
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @deprecated Use {@link #sha1(String)}
+ */
+ @Deprecated
+ public static byte[] sha(final String data) {
+ return sha1(data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @since 1.7
+ */
+ public static byte[] sha1(final byte[] data) {
+ return getSha1Digest().digest(data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @since 1.11
+ */
+ public static byte[] sha1(final ByteBuffer data) {
+ return digest(getSha1Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.7
+ */
+ public static byte[] sha1(final InputStream data) throws IOException {
+ return digest(getSha1Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return SHA-1 digest
+ */
+ public static byte[] sha1(final String data) {
+ return sha1(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @since 1.7
+ */
+ public static String sha1Hex(final byte[] data) {
+ return Hex.encodeHexString(sha1(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @since 1.11
+ */
+ public static String sha1Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(sha1(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.7
+ */
+ public static String sha1Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(sha1(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @since 1.7
+ */
+ public static String sha1Hex(final String data) {
+ return Hex.encodeHexString(sha1(data));
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a byte[]
.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static byte[] sha224(final byte[] data) {
+ return getSha224Digest().digest(data);
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static byte[] sha224(final ByteBuffer data) {
+ return digest(getSha224Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a byte[]
.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static byte[] sha224(final InputStream data) throws IOException {
+ return digest(getSha224Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a byte[]
.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return SHA-224 digest
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static byte[] sha224(final String data) {
+ return sha224(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a hex string.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest as a hex string
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static String sha224Hex(final byte[] data) {
+ return Hex.encodeHexString(sha224(data));
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a hex string.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest as a hex string
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static String sha224Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(sha224(data));
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a hex string.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static String sha224Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(sha224(data));
+ }
+
+ /**
+ * Calculates the SHA-224 digest and returns the value as a hex string.
+ *
+ * Throws a {@link IllegalArgumentException} on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-224 digest as a hex string
+ * @throws IllegalArgumentException thrown on JRE versions prior to 1.8.0.
+ * @since 1.11
+ */
+ public static String sha224Hex(final String data) {
+ return Hex.encodeHexString(sha224(data));
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest
+ * @since 1.4
+ */
+ public static byte[] sha256(final byte[] data) {
+ return getSha256Digest().digest(data);
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest
+ * @since 1.11
+ */
+ public static byte[] sha256(final ByteBuffer data) {
+ return digest(getSha256Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static byte[] sha256(final InputStream data) throws IOException {
+ return digest(getSha256Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return SHA-256 digest
+ * @since 1.4
+ */
+ public static byte[] sha256(final String data) {
+ return sha256(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha256Hex(final byte[] data) {
+ return Hex.encodeHexString(sha256(data));
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest as a hex string
+ * @since 1.11
+ */
+ public static String sha256Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(sha256(data));
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static String sha256Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(sha256(data));
+ }
+
+ /**
+ * Calculates the SHA-256 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-256 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha256Hex(final String data) {
+ return Hex.encodeHexString(sha256(data));
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest
+ * @since 1.4
+ */
+ public static byte[] sha384(final byte[] data) {
+ return getSha384Digest().digest(data);
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest
+ * @since 1.11
+ */
+ public static byte[] sha384(final ByteBuffer data) {
+ return digest(getSha384Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static byte[] sha384(final InputStream data) throws IOException {
+ return digest(getSha384Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return SHA-384 digest
+ * @since 1.4
+ */
+ public static byte[] sha384(final String data) {
+ return sha384(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha384Hex(final byte[] data) {
+ return Hex.encodeHexString(sha384(data));
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest as a hex string
+ * @since 1.11
+ */
+ public static String sha384Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(sha384(data));
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static String sha384Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(sha384(data));
+ }
+
+ /**
+ * Calculates the SHA-384 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-384 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha384Hex(final String data) {
+ return Hex.encodeHexString(sha384(data));
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest
+ * @since 1.4
+ */
+ public static byte[] sha512(final byte[] data) {
+ return getSha512Digest().digest(data);
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a byte[]
.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest
+ * @since 1.11
+ */
+ public static byte[] sha512(final ByteBuffer data) {
+ return digest(getSha512Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static byte[] sha512(final InputStream data) throws IOException {
+ return digest(getSha512Digest(), data);
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a byte[]
.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return SHA-512 digest
+ * @since 1.4
+ */
+ public static byte[] sha512(final String data) {
+ return sha512(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha512Hex(final byte[] data) {
+ return Hex.encodeHexString(sha512(data));
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest as a hex string
+ * @since 1.11
+ */
+ public static String sha512Hex(final ByteBuffer data) {
+ return Hex.encodeHexString(sha512(data));
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ */
+ public static String sha512Hex(final InputStream data) throws IOException {
+ return Hex.encodeHexString(sha512(data));
+ }
+
+ /**
+ * Calculates the SHA-512 digest and returns the value as a hex string.
+ *
+ * Throws a RuntimeException
on JRE versions prior to 1.4.0.
+ *
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-512 digest as a hex string
+ * @since 1.4
+ */
+ public static String sha512Hex(final String data) {
+ return Hex.encodeHexString(sha512(data));
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @deprecated Use {@link #sha1Hex(byte[])}
+ */
+ @Deprecated
+ public static String shaHex(final byte[] data) {
+ return sha1Hex(data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.4
+ * @deprecated Use {@link #sha1Hex(InputStream)}
+ */
+ @Deprecated
+ public static String shaHex(final InputStream data) throws IOException {
+ return sha1Hex(data);
+ }
+
+ /**
+ * Calculates the SHA-1 digest and returns the value as a hex string.
+ *
+ * @param data
+ * Data to digest
+ * @return SHA-1 digest as a hex string
+ * @deprecated Use {@link #sha1Hex(String)}
+ */
+ @Deprecated
+ public static String shaHex(final String data) {
+ return sha1Hex(data);
+ }
+
+ /**
+ * Updates the given {@link MessageDigest}.
+ *
+ * @param messageDigest
+ * the {@link MessageDigest} to update
+ * @param valueToDigest
+ * the value to update the {@link MessageDigest} with
+ * @return the updated {@link MessageDigest}
+ * @since 1.7
+ */
+ public static MessageDigest updateDigest(final MessageDigest messageDigest, final byte[] valueToDigest) {
+ messageDigest.update(valueToDigest);
+ return messageDigest;
+ }
+
+ /**
+ * Updates the given {@link MessageDigest}.
+ *
+ * @param messageDigest
+ * the {@link MessageDigest} to update
+ * @param valueToDigest
+ * the value to update the {@link MessageDigest} with
+ * @return the updated {@link MessageDigest}
+ * @since 1.11
+ */
+ public static MessageDigest updateDigest(final MessageDigest messageDigest, final ByteBuffer valueToDigest) {
+ messageDigest.update(valueToDigest);
+ return messageDigest;
+ }
+
+ /**
+ * Reads through an InputStream and updates the digest for the data
+ *
+ * @param digest
+ * The MessageDigest to use (e.g. MD5)
+ * @param data
+ * Data to digest
+ * @return the digest
+ * @throws IOException
+ * On error reading from the stream
+ * @since 1.8
+ */
+ public static MessageDigest updateDigest(final MessageDigest digest, final InputStream data) throws IOException {
+ final byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
+ int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH);
+
+ while (read > -1) {
+ digest.update(buffer, 0, read);
+ read = data.read(buffer, 0, STREAM_BUFFER_LENGTH);
+ }
+
+ return digest;
+ }
+
+ /**
+ * Updates the given {@link MessageDigest}.
+ *
+ * @param messageDigest
+ * the {@link MessageDigest} to update
+ * @param valueToDigest
+ * the value to update the {@link MessageDigest} with;
+ * converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return the updated {@link MessageDigest}
+ * @since 1.7
+ */
+ public static MessageDigest updateDigest(final MessageDigest messageDigest, final String valueToDigest) {
+ messageDigest.update(StringUtils.getBytesUtf8(valueToDigest));
+ return messageDigest;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/HmacAlgorithms.java b/src/main/java/org/apache/commons/codec/digest/HmacAlgorithms.java
new file mode 100644
index 00000000..5dd30d86
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/HmacAlgorithms.java
@@ -0,0 +1,94 @@
+/*
+ * 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.commons.codec.digest;
+
+/**
+ * Standard {@link HmacUtils} algorithm names from the Java Cryptography Architecture Standard Algorithm Name
+ * Documentation .
+ *
+ *
+ * Note: Not all JCE implementations supports all algorithms in this enum.
+ *
+ *
+ * @see Java Cryptography
+ * Architecture Standard Algorithm Name Documentation
+ * @since 1.10
+ * @version $Id$
+ */
+public enum HmacAlgorithms {
+
+ /**
+ * The HmacMD5 Message Authentication Code (MAC) algorithm specified in RFC 2104 and RFC 1321.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ */
+ HMAC_MD5("HmacMD5"),
+
+ /**
+ * The HmacSHA1 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ */
+ HMAC_SHA_1("HmacSHA1"),
+
+ /**
+ * The HmacSHA256 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ */
+ HMAC_SHA_256("HmacSHA256"),
+
+ /**
+ * The HmacSHA384 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2.
+ *
+ * Every implementation of the Java platform is not required to support this Mac algorithm.
+ *
+ */
+ HMAC_SHA_384("HmacSHA384"),
+
+ /**
+ * The HmacSHA512 Message Authentication Code (MAC) algorithm specified in RFC 2104 and FIPS PUB 180-2.
+ *
+ * Every implementation of the Java platform is not required to support this Mac algorithm.
+ *
+ */
+ HMAC_SHA_512("HmacSHA512");
+
+ private final String algorithm;
+
+ private HmacAlgorithms(final String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ /**
+ * The algorithm name
+ *
+ * @see Java
+ * Cryptography Architecture Sun Providers Documentation
+ * @return The algorithm name ("HmacSHA512" for example)
+ */
+ @Override
+ public String toString() {
+ return algorithm;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/HmacUtils.java b/src/main/java/org/apache/commons/codec/digest/HmacUtils.java
new file mode 100644
index 00000000..425db4ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/HmacUtils.java
@@ -0,0 +1,794 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Simplifies common {@link javax.crypto.Mac} tasks. This class is immutable and thread-safe.
+ *
+ *
+ *
+ * Note: Not all JCE implementations supports all algorithms. If not supported, an IllegalArgumentException is
+ * thrown.
+ *
+ *
+ * @since 1.10
+ * @version $Id$
+ */
+public final class HmacUtils {
+
+ private static final int STREAM_BUFFER_LENGTH = 1024;
+
+ /**
+ * Returns an initialized Mac
for the HmacMD5 algorithm.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getHmacMd5(final byte[] key) {
+ return getInitializedMac(HmacAlgorithms.HMAC_MD5, key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the HmacSHA1 algorithm.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getHmacSha1(final byte[] key) {
+ return getInitializedMac(HmacAlgorithms.HMAC_SHA_1, key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the HmacSHA256 algorithm.
+ *
+ * Every implementation of the Java platform is required to support this standard Mac algorithm.
+ *
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getHmacSha256(final byte[] key) {
+ return getInitializedMac(HmacAlgorithms.HMAC_SHA_256, key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the HmacSHA384 algorithm.
+ *
+ * Every implementation of the Java platform is not required to support this Mac algorithm.
+ *
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getHmacSha384(final byte[] key) {
+ return getInitializedMac(HmacAlgorithms.HMAC_SHA_384, key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the HmacSHA512 algorithm.
+ *
+ * Every implementation of the Java platform is not required to support this Mac algorithm.
+ *
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getHmacSha512(final byte[] key) {
+ return getInitializedMac(HmacAlgorithms.HMAC_SHA_512, key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the given algorithm
.
+ *
+ * @param algorithm
+ * the name of the algorithm requested. See Appendix
+ * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm
+ * names.
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getInitializedMac(final HmacAlgorithms algorithm, final byte[] key) {
+ return getInitializedMac(algorithm.toString(), key);
+ }
+
+ /**
+ * Returns an initialized Mac
for the given algorithm
.
+ *
+ * @param algorithm
+ * the name of the algorithm requested. See Appendix
+ * A in the Java Cryptography Architecture Reference Guide for information about standard algorithm
+ * names.
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @return A Mac instance initialized with the given key.
+ * @see Mac#getInstance(String)
+ * @see Mac#init(Key)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static Mac getInitializedMac(final String algorithm, final byte[] key) {
+
+ if (key == null) {
+ throw new IllegalArgumentException("Null key");
+ }
+
+ try {
+ final SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
+ final Mac mac = Mac.getInstance(algorithm);
+ mac.init(keySpec);
+ return mac;
+ } catch (final NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ } catch (final InvalidKeyException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ // hmacMd5
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacMD5 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacMd5(final byte[] key, final byte[] valueToDigest) {
+ try {
+ return getHmacMd5(key).doFinal(valueToDigest);
+ } catch (final IllegalStateException e) {
+ // cannot happen
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacMD5 MAC for the given key and value
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacMd5(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return updateHmac(getHmacMd5(key), valueToDigest).doFinal();
+ }
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacMD5 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacMd5(final String key, final String valueToDigest) {
+ return hmacMd5(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest));
+ }
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacMD5 MAC for the given key and value as a hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacMd5Hex(final byte[] key, final byte[] valueToDigest) {
+ return Hex.encodeHexString(hmacMd5(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacMD5 MAC for the given key and value as a hex string (lowercase)
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacMd5Hex(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return Hex.encodeHexString(hmacMd5(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacMD5 Message Authentication Code (MAC) as a hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacMD5 MAC for the given key and value as a hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacMd5Hex(final String key, final String valueToDigest) {
+ return Hex.encodeHexString(hmacMd5(key, valueToDigest));
+ }
+
+ // hmacSha1
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA1 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha1(final byte[] key, final byte[] valueToDigest) {
+ try {
+ return getHmacSha1(key).doFinal(valueToDigest);
+ } catch (final IllegalStateException e) {
+ // cannot happen
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA1 MAC for the given key and value
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha1(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return updateHmac(getHmacSha1(key), valueToDigest).doFinal();
+ }
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA1 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha1(final String key, final String valueToDigest) {
+ return hmacSha1(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA1 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha1Hex(final byte[] key, final byte[] valueToDigest) {
+ return Hex.encodeHexString(hmacSha1(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA1 MAC for the given key and value as hex string (lowercase)
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha1Hex(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return Hex.encodeHexString(hmacSha1(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA1 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA1 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha1Hex(final String key, final String valueToDigest) {
+ return Hex.encodeHexString(hmacSha1(key, valueToDigest));
+ }
+
+ // hmacSha256
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA256 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha256(final byte[] key, final byte[] valueToDigest) {
+ try {
+ return getHmacSha256(key).doFinal(valueToDigest);
+ } catch (final IllegalStateException e) {
+ // cannot happen
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA256 MAC for the given key and value
+ * @throws IOException
+ * If an I/O error occurs.
+s * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha256(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return updateHmac(getHmacSha256(key), valueToDigest).doFinal();
+ }
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA256 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha256(final String key, final String valueToDigest) {
+ return hmacSha256(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA256 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha256Hex(final byte[] key, final byte[] valueToDigest) {
+ return Hex.encodeHexString(hmacSha256(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA256 MAC for the given key and value as hex string (lowercase)
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha256Hex(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return Hex.encodeHexString(hmacSha256(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA256 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA256 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha256Hex(final String key, final String valueToDigest) {
+ return Hex.encodeHexString(hmacSha256(key, valueToDigest));
+ }
+
+ // hmacSha384
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA384 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha384(final byte[] key, final byte[] valueToDigest) {
+ try {
+ return getHmacSha384(key).doFinal(valueToDigest);
+ } catch (final IllegalStateException e) {
+ // cannot happen
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA384 MAC for the given key and value
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha384(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return updateHmac(getHmacSha384(key), valueToDigest).doFinal();
+ }
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA384 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha384(final String key, final String valueToDigest) {
+ return hmacSha384(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA384 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha384Hex(final byte[] key, final byte[] valueToDigest) {
+ return Hex.encodeHexString(hmacSha384(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA384 MAC for the given key and value as hex string (lowercase)
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha384Hex(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return Hex.encodeHexString(hmacSha384(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA384 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA384 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha384Hex(final String key, final String valueToDigest) {
+ return Hex.encodeHexString(hmacSha384(key, valueToDigest));
+ }
+
+ // hmacSha512
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA512 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha512(final byte[] key, final byte[] valueToDigest) {
+ try {
+ return getHmacSha512(key).doFinal(valueToDigest);
+ } catch (final IllegalStateException e) {
+ // cannot happen
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA512 MAC for the given key and value
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha512(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return updateHmac(getHmacSha512(key), valueToDigest).doFinal();
+ }
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA512 MAC for the given key and value
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static byte[] hmacSha512(final String key, final String valueToDigest) {
+ return hmacSha512(StringUtils.getBytesUtf8(key), StringUtils.getBytesUtf8(valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA512 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha512Hex(final byte[] key, final byte[] valueToDigest) {
+ return Hex.encodeHexString(hmacSha512(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return HmacSHA512 MAC for the given key and value as hex string (lowercase)
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha512Hex(final byte[] key, final InputStream valueToDigest) throws IOException {
+ return Hex.encodeHexString(hmacSha512(key, valueToDigest));
+ }
+
+ /**
+ * Returns a HmacSHA512 Message Authentication Code (MAC) as hex string (lowercase) for the given key and value.
+ *
+ * @param key
+ * They key for the keyed digest (must not be null)
+ * @param valueToDigest
+ * The value (data) which should to digest (maybe empty or null)
+ * @return HmacSHA512 MAC for the given key and value as hex string (lowercase)
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught or key is null or key is invalid.
+ */
+ public static String hmacSha512Hex(final String key, final String valueToDigest) {
+ return Hex.encodeHexString(hmacSha512(key, valueToDigest));
+ }
+
+ // update
+
+ /**
+ * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized
+ *
+ * @param mac
+ * the initialized {@link Mac} to update
+ * @param valueToDigest
+ * the value to update the {@link Mac} with (maybe null or empty)
+ * @return the updated {@link Mac}
+ * @throws IllegalStateException
+ * if the Mac was not initialized
+ * @since 1.x
+ */
+ public static Mac updateHmac(final Mac mac, final byte[] valueToDigest) {
+ mac.reset();
+ mac.update(valueToDigest);
+ return mac;
+ }
+
+ /**
+ * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized
+ *
+ * @param mac
+ * the initialized {@link Mac} to update
+ * @param valueToDigest
+ * the value to update the {@link Mac} with
+ *
+ * The InputStream must not be null and will not be closed
+ *
+ * @return the updated {@link Mac}
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws IllegalStateException
+ * If the Mac was not initialized
+ * @since 1.x
+ */
+ public static Mac updateHmac(final Mac mac, final InputStream valueToDigest) throws IOException {
+ mac.reset();
+ final byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
+ int read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH);
+
+ while (read > -1) {
+ mac.update(buffer, 0, read);
+ read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH);
+ }
+
+ return mac;
+ }
+
+ /**
+ * Updates the given {@link Mac}. This generates a digest for valueToDigest and the key the Mac was initialized
+ *
+ * @param mac
+ * the initialized {@link Mac} to update
+ * @param valueToDigest
+ * the value to update the {@link Mac} with (maybe null or empty)
+ * @return the updated {@link Mac}
+ * @throws IllegalStateException
+ * if the Mac was not initialized
+ * @since 1.x
+ */
+ public static Mac updateHmac(final Mac mac, final String valueToDigest) {
+ mac.reset();
+ mac.update(StringUtils.getBytesUtf8(valueToDigest));
+ return mac;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java b/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java
new file mode 100644
index 00000000..09625496
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java
@@ -0,0 +1,302 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm.
+ *
+ * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at:
+ * crypt-md5.c @ freebsd.org
+ *
+ * Source:
+ *
+ *
+ * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
+ *
+ *
+ * Conversion to Kotlin and from there to Java in 2012.
+ *
+ * The C style comments are from the original C code, the ones with "//" from the port.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+public class Md5Crypt {
+
+ /** The Identifier of the Apache variant. */
+ static final String APR1_PREFIX = "$apr1$";
+
+ /** The number of bytes of the final hash. */
+ private static final int BLOCKSIZE = 16;
+
+ /** The Identifier of this crypt() variant. */
+ static final String MD5_PREFIX = "$1$";
+
+ /** The number of rounds of the big loop. */
+ private static final int ROUNDS = 1000;
+
+ /**
+ * See {@link #apr1Crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @return the hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught. *
+ */
+ public static String apr1Crypt(final byte[] keyBytes) {
+ return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8));
+ }
+
+ /**
+ * See {@link #apr1Crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @param salt An APR1 salt.
+ * @return the hash value
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String apr1Crypt(final byte[] keyBytes, String salt) {
+ // to make the md5Crypt regex happy
+ if (salt != null && !salt.startsWith(APR1_PREFIX)) {
+ salt = APR1_PREFIX + salt;
+ }
+ return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX);
+ }
+
+ /**
+ * See {@link #apr1Crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @return the hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String apr1Crypt(final String keyBytes) {
+ return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value.
+ *
+ * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt
+ * prefix.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @param salt
+ * salt string including the prefix and optionally garbage at the end. Will be generated randomly if
+ * null.
+ * @return the hash value
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String apr1Crypt(final String keyBytes, final String salt) {
+ return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8), salt);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$1$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @return the hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String md5Crypt(final byte[] keyBytes) {
+ return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8));
+ }
+
+ /**
+ * Generates a libc crypt() compatible "$1$" MD5 based hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @param salt
+ * salt string including the prefix and optionally garbage at the end. Will be generated randomly if
+ * null.
+ * @return the hash value
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String md5Crypt(final byte[] keyBytes, final String salt) {
+ return md5Crypt(keyBytes, salt, MD5_PREFIX);
+ }
+
+ /**
+ * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext string to hash.
+ * @param salt May be null.
+ * @param prefix salt prefix
+ * @return the hash value
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) {
+ final int keyLen = keyBytes.length;
+
+ // Extract the real salt from the given string which can be a complete hash string.
+ String saltString;
+ if (salt == null) {
+ saltString = B64.getRandomSalt(8);
+ } else {
+ final Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*");
+ final Matcher m = p.matcher(salt);
+ if (m == null || !m.find()) {
+ throw new IllegalArgumentException("Invalid salt value: " + salt);
+ }
+ saltString = m.group(1);
+ }
+ final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8);
+
+ final MessageDigest ctx = DigestUtils.getMd5Digest();
+
+ /*
+ * The password first, since that is what is most unknown
+ */
+ ctx.update(keyBytes);
+
+ /*
+ * Then our magic string
+ */
+ ctx.update(prefix.getBytes(Charsets.UTF_8));
+
+ /*
+ * Then the raw salt
+ */
+ ctx.update(saltBytes);
+
+ /*
+ * Then just as many characters of the MD5(pw,salt,pw)
+ */
+ MessageDigest ctx1 = DigestUtils.getMd5Digest();
+ ctx1.update(keyBytes);
+ ctx1.update(saltBytes);
+ ctx1.update(keyBytes);
+ byte[] finalb = ctx1.digest();
+ int ii = keyLen;
+ while (ii > 0) {
+ ctx.update(finalb, 0, ii > 16 ? 16 : ii);
+ ii -= 16;
+ }
+
+ /*
+ * Don't leave anything around in vm they could use.
+ */
+ Arrays.fill(finalb, (byte) 0);
+
+ /*
+ * Then something really weird...
+ */
+ ii = keyLen;
+ final int j = 0;
+ while (ii > 0) {
+ if ((ii & 1) == 1) {
+ ctx.update(finalb[j]);
+ } else {
+ ctx.update(keyBytes[j]);
+ }
+ ii >>= 1;
+ }
+
+ /*
+ * Now make the output string
+ */
+ final StringBuilder passwd = new StringBuilder(prefix + saltString + "$");
+ finalb = ctx.digest();
+
+ /*
+ * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 msec, so you would
+ * need 30 seconds to build a 1000 entry dictionary...
+ */
+ for (int i = 0; i < ROUNDS; i++) {
+ ctx1 = DigestUtils.getMd5Digest();
+ if ((i & 1) != 0) {
+ ctx1.update(keyBytes);
+ } else {
+ ctx1.update(finalb, 0, BLOCKSIZE);
+ }
+
+ if (i % 3 != 0) {
+ ctx1.update(saltBytes);
+ }
+
+ if (i % 7 != 0) {
+ ctx1.update(keyBytes);
+ }
+
+ if ((i & 1) != 0) {
+ ctx1.update(finalb, 0, BLOCKSIZE);
+ } else {
+ ctx1.update(keyBytes);
+ }
+ finalb = ctx1.digest();
+ }
+
+ // The following was nearly identical to the Sha2Crypt code.
+ // Again, the buflen is not really needed.
+ // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1;
+ B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd);
+ B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd);
+ B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd);
+ B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd);
+ B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd);
+ B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd);
+
+ /*
+ * Don't leave anything around in vm they could use.
+ */
+ // Is there a better way to do this with the JVM?
+ ctx.reset();
+ ctx1.reset();
+ Arrays.fill(keyBytes, (byte) 0);
+ Arrays.fill(saltBytes, (byte) 0);
+ Arrays.fill(finalb, (byte) 0);
+
+ return passwd.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/MessageDigestAlgorithms.java b/src/main/java/org/apache/commons/codec/digest/MessageDigestAlgorithms.java
new file mode 100644
index 00000000..981dfe82
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/MessageDigestAlgorithms.java
@@ -0,0 +1,81 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.security.MessageDigest;
+
+/**
+ * Standard {@link MessageDigest} algorithm names from the Java Cryptography Architecture Standard Algorithm Name
+ * Documentation .
+ *
+ * This class is immutable and thread-safe.
+ *
+ * TODO 2.0 This should be an enum.
+ *
+ * @see Java Cryptography
+ * Architecture Standard Algorithm Name Documentation
+ * @since 1.7
+ * @version $Id$
+ */
+public class MessageDigestAlgorithms {
+
+ private MessageDigestAlgorithms() {
+ // cannot be instantiated.
+ }
+
+ /**
+ * The MD2 message digest algorithm defined in RFC 1319.
+ */
+ public static final String MD2 = "MD2";
+
+ /**
+ * The MD5 message digest algorithm defined in RFC 1321.
+ */
+ public static final String MD5 = "MD5";
+
+ /**
+ * The SHA-1 hash algorithm defined in the FIPS PUB 180-2.
+ */
+ public static final String SHA_1 = "SHA-1";
+
+ /**
+ * The SHA-224 hash algorithm defined in the FIPS PUB 180-4.
+ *
+ * Java 8 only.
+ *
+ *
+ * @since 1.11
+ */
+ public static final String SHA_224 = "SHA-224";
+
+ /**
+ * The SHA-256 hash algorithm defined in the FIPS PUB 180-2.
+ */
+ public static final String SHA_256 = "SHA-256";
+
+ /**
+ * The SHA-384 hash algorithm defined in the FIPS PUB 180-2.
+ */
+ public static final String SHA_384 = "SHA-384";
+
+ /**
+ * The SHA-512 hash algorithm defined in the FIPS PUB 180-2.
+ */
+ public static final String SHA_512 = "SHA-512";
+
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java b/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
new file mode 100644
index 00000000..6e568d7e
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
@@ -0,0 +1,545 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * SHA2-based Unix crypt implementation.
+ *
+ * Based on the C implementation released into the Public Domain by Ulrich Drepper <drepper@redhat.com>
+ * http://www.akkadia.org/drepper/SHA-crypt.txt
+ *
+ * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers <ch@lathspell.de> and likewise put
+ * into the Public Domain.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+public class Sha2Crypt {
+
+ /** Default number of rounds if not explicitly specified. */
+ private static final int ROUNDS_DEFAULT = 5000;
+
+ /** Maximum number of rounds. */
+ private static final int ROUNDS_MAX = 999999999;
+
+ /** Minimum number of rounds. */
+ private static final int ROUNDS_MIN = 1000;
+
+ /** Prefix for optional rounds specification. */
+ private static final String ROUNDS_PREFIX = "rounds=";
+
+ /** The number of bytes the final hash value will have (SHA-256 variant). */
+ private static final int SHA256_BLOCKSIZE = 32;
+
+ /** The prefixes that can be used to identify this crypt() variant (SHA-256). */
+ static final String SHA256_PREFIX = "$5$";
+
+ /** The number of bytes the final hash value will have (SHA-512 variant). */
+ private static final int SHA512_BLOCKSIZE = 64;
+
+ /** The prefixes that can be used to identify this crypt() variant (SHA-512). */
+ static final String SHA512_PREFIX = "$6$";
+
+ /** The pattern to match valid salt values. */
+ private static final Pattern SALT_PATTERN = Pattern
+ .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*");
+
+ /**
+ * Generates a libc crypt() compatible "$5$" hash value with random salt.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext to hash
+ * @return complete hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String sha256Crypt(final byte[] keyBytes) {
+ return sha256Crypt(keyBytes, null);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$5$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext to hash
+ * @param salt
+ * real salt value without prefix or "rounds="
+ * @return complete hash value including salt
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String sha256Crypt(final byte[] keyBytes, String salt) {
+ if (salt == null) {
+ salt = SHA256_PREFIX + B64.getRandomSalt(8);
+ }
+ return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE, MessageDigestAlgorithms.SHA_256);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value.
+ *
+ * This is a nearly line by line conversion of the original C function. The numbered comments are from the algorithm
+ * description, the short C-style ones from the original C code and the ones with "Remark" from me.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext to hash
+ * @param salt
+ * real salt value without prefix or "rounds="
+ * @param saltPrefix
+ * either $5$ or $6$
+ * @param blocksize
+ * a value that differs between $5$ and $6$
+ * @param algorithm
+ * {@link MessageDigest} algorithm identifier string
+ * @return complete hash value including prefix and salt
+ * @throws IllegalArgumentException
+ * if the given salt is null
or does not match the allowed pattern
+ * @throws IllegalArgumentException
+ * when a {@link NoSuchAlgorithmException} is caught
+ * @see MessageDigestAlgorithms
+ */
+ private static String sha2Crypt(final byte[] keyBytes, final String salt, final String saltPrefix,
+ final int blocksize, final String algorithm) {
+
+ final int keyLen = keyBytes.length;
+
+ // Extracts effective salt and the number of rounds from the given salt.
+ int rounds = ROUNDS_DEFAULT;
+ boolean roundsCustom = false;
+ if (salt == null) {
+ throw new IllegalArgumentException("Salt must not be null");
+ }
+
+ final Matcher m = SALT_PATTERN.matcher(salt);
+ if (m == null || !m.find()) {
+ throw new IllegalArgumentException("Invalid salt value: " + salt);
+ }
+ if (m.group(3) != null) {
+ rounds = Integer.parseInt(m.group(3));
+ rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds));
+ roundsCustom = true;
+ }
+ final String saltString = m.group(4);
+ final byte[] saltBytes = saltString.getBytes(Charsets.UTF_8);
+ final int saltLen = saltBytes.length;
+
+ // 1. start digest A
+ // Prepare for the real work.
+ MessageDigest ctx = DigestUtils.getDigest(algorithm);
+
+ // 2. the password string is added to digest A
+ /*
+ * Add the key string.
+ */
+ ctx.update(keyBytes);
+
+ // 3. the salt string is added to digest A. This is just the salt string
+ // itself without the enclosing '$', without the magic salt_prefix $5$ and
+ // $6$ respectively and without the rounds= specification.
+ //
+ // NB: the MD5 algorithm did add the $1$ salt_prefix. This is not deemed
+ // necessary since it is a constant string and does not add security
+ // and /possibly/ allows a plain text attack. Since the rounds=
+ // specification should never be added this would also create an
+ // inconsistency.
+ /*
+ * The last part is the salt string. This must be at most 16 characters and it ends at the first `$' character
+ * (for compatibility with existing implementations).
+ */
+ ctx.update(saltBytes);
+
+ // 4. start digest B
+ /*
+ * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final result will be added to the first
+ * context.
+ */
+ MessageDigest altCtx = DigestUtils.getDigest(algorithm);
+
+ // 5. add the password to digest B
+ /*
+ * Add key.
+ */
+ altCtx.update(keyBytes);
+
+ // 6. add the salt string to digest B
+ /*
+ * Add salt.
+ */
+ altCtx.update(saltBytes);
+
+ // 7. add the password again to digest B
+ /*
+ * Add key again.
+ */
+ altCtx.update(keyBytes);
+
+ // 8. finish digest B
+ /*
+ * Now get result of this (32 bytes) and add it to the other context.
+ */
+ byte[] altResult = altCtx.digest();
+
+ // 9. For each block of 32 or 64 bytes in the password string (excluding
+ // the terminating NUL in the C representation), add digest B to digest A
+ /*
+ * Add for any character in the key one byte of the alternate sum.
+ */
+ /*
+ * (Remark: the C code comment seems wrong for key length > 32!)
+ */
+ int cnt = keyBytes.length;
+ while (cnt > blocksize) {
+ ctx.update(altResult, 0, blocksize);
+ cnt -= blocksize;
+ }
+
+ // 10. For the remaining N bytes of the password string add the first
+ // N bytes of digest B to digest A
+ ctx.update(altResult, 0, cnt);
+
+ // 11. For each bit of the binary representation of the length of the
+ // password string up to and including the highest 1-digit, starting
+ // from to lowest bit position (numeric value 1):
+ //
+ // a) for a 1-digit add digest B to digest A
+ //
+ // b) for a 0-digit add the password string
+ //
+ // NB: this step differs significantly from the MD5 algorithm. It
+ // adds more randomness.
+ /*
+ * Take the binary representation of the length of the key and for every 1 add the alternate sum, for every 0
+ * the key.
+ */
+ cnt = keyBytes.length;
+ while (cnt > 0) {
+ if ((cnt & 1) != 0) {
+ ctx.update(altResult, 0, blocksize);
+ } else {
+ ctx.update(keyBytes);
+ }
+ cnt >>= 1;
+ }
+
+ // 12. finish digest A
+ /*
+ * Create intermediate result.
+ */
+ altResult = ctx.digest();
+
+ // 13. start digest DP
+ /*
+ * Start computation of P byte sequence.
+ */
+ altCtx = DigestUtils.getDigest(algorithm);
+
+ // 14. for every byte in the password (excluding the terminating NUL byte
+ // in the C representation of the string)
+ //
+ // add the password to digest DP
+ /*
+ * For every character in the password add the entire password.
+ */
+ for (int i = 1; i <= keyLen; i++) {
+ altCtx.update(keyBytes);
+ }
+
+ // 15. finish digest DP
+ /*
+ * Finish the digest.
+ */
+ byte[] tempResult = altCtx.digest();
+
+ // 16. produce byte sequence P of the same length as the password where
+ //
+ // a) for each block of 32 or 64 bytes of length of the password string
+ // the entire digest DP is used
+ //
+ // b) for the remaining N (up to 31 or 63) bytes use the first N
+ // bytes of digest DP
+ /*
+ * Create byte sequence P.
+ */
+ final byte[] pBytes = new byte[keyLen];
+ int cp = 0;
+ while (cp < keyLen - blocksize) {
+ System.arraycopy(tempResult, 0, pBytes, cp, blocksize);
+ cp += blocksize;
+ }
+ System.arraycopy(tempResult, 0, pBytes, cp, keyLen - cp);
+
+ // 17. start digest DS
+ /*
+ * Start computation of S byte sequence.
+ */
+ altCtx = DigestUtils.getDigest(algorithm);
+
+ // 18. repeast the following 16+A[0] times, where A[0] represents the first
+ // byte in digest A interpreted as an 8-bit unsigned value
+ //
+ // add the salt to digest DS
+ /*
+ * For every character in the password add the entire password.
+ */
+ for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) {
+ altCtx.update(saltBytes);
+ }
+
+ // 19. finish digest DS
+ /*
+ * Finish the digest.
+ */
+ tempResult = altCtx.digest();
+
+ // 20. produce byte sequence S of the same length as the salt string where
+ //
+ // a) for each block of 32 or 64 bytes of length of the salt string
+ // the entire digest DS is used
+ //
+ // b) for the remaining N (up to 31 or 63) bytes use the first N
+ // bytes of digest DS
+ /*
+ * Create byte sequence S.
+ */
+ // Remark: The salt is limited to 16 chars, how does this make sense?
+ final byte[] sBytes = new byte[saltLen];
+ cp = 0;
+ while (cp < saltLen - blocksize) {
+ System.arraycopy(tempResult, 0, sBytes, cp, blocksize);
+ cp += blocksize;
+ }
+ System.arraycopy(tempResult, 0, sBytes, cp, saltLen - cp);
+
+ // 21. repeat a loop according to the number specified in the rounds=
+ // specification in the salt (or the default value if none is
+ // present). Each round is numbered, starting with 0 and up to N-1.
+ //
+ // The loop uses a digest as input. In the first round it is the
+ // digest produced in step 12. In the latter steps it is the digest
+ // produced in step 21.h. The following text uses the notation
+ // "digest A/C" to describe this behavior.
+ /*
+ * Repeatedly run the collected hash value through sha512 to burn CPU cycles.
+ */
+ for (int i = 0; i <= rounds - 1; i++) {
+ // a) start digest C
+ /*
+ * New context.
+ */
+ ctx = DigestUtils.getDigest(algorithm);
+
+ // b) for odd round numbers add the byte sequense P to digest C
+ // c) for even round numbers add digest A/C
+ /*
+ * Add key or last result.
+ */
+ if ((i & 1) != 0) {
+ ctx.update(pBytes, 0, keyLen);
+ } else {
+ ctx.update(altResult, 0, blocksize);
+ }
+
+ // d) for all round numbers not divisible by 3 add the byte sequence S
+ /*
+ * Add salt for numbers not divisible by 3.
+ */
+ if (i % 3 != 0) {
+ ctx.update(sBytes, 0, saltLen);
+ }
+
+ // e) for all round numbers not divisible by 7 add the byte sequence P
+ /*
+ * Add key for numbers not divisible by 7.
+ */
+ if (i % 7 != 0) {
+ ctx.update(pBytes, 0, keyLen);
+ }
+
+ // f) for odd round numbers add digest A/C
+ // g) for even round numbers add the byte sequence P
+ /*
+ * Add key or last result.
+ */
+ if ((i & 1) != 0) {
+ ctx.update(altResult, 0, blocksize);
+ } else {
+ ctx.update(pBytes, 0, keyLen);
+ }
+
+ // h) finish digest C.
+ /*
+ * Create intermediate result.
+ */
+ altResult = ctx.digest();
+ }
+
+ // 22. Produce the output string. This is an ASCII string of the maximum
+ // size specified above, consisting of multiple pieces:
+ //
+ // a) the salt salt_prefix, $5$ or $6$ respectively
+ //
+ // b) the rounds= specification, if one was present in the input
+ // salt string. A trailing '$' is added in this case to separate
+ // the rounds specification from the following text.
+ //
+ // c) the salt string truncated to 16 characters
+ //
+ // d) a '$' character
+ /*
+ * Now we can construct the result string. It consists of three parts.
+ */
+ final StringBuilder buffer = new StringBuilder(saltPrefix);
+ if (roundsCustom) {
+ buffer.append(ROUNDS_PREFIX);
+ buffer.append(rounds);
+ buffer.append("$");
+ }
+ buffer.append(saltString);
+ buffer.append("$");
+
+ // e) the base-64 encoded final C digest. The encoding used is as
+ // follows:
+ // [...]
+ //
+ // Each group of three bytes from the digest produces four
+ // characters as output:
+ //
+ // 1. character: the six low bits of the first byte
+ // 2. character: the two high bits of the first byte and the
+ // four low bytes from the second byte
+ // 3. character: the four high bytes from the second byte and
+ // the two low bits from the third byte
+ // 4. character: the six high bits from the third byte
+ //
+ // The groups of three bytes are as follows (in this sequence).
+ // These are the indices into the byte array containing the
+ // digest, starting with index 0. For the last group there are
+ // not enough bytes left in the digest and the value zero is used
+ // in its place. This group also produces only three or two
+ // characters as output for SHA-512 and SHA-512 respectively.
+
+ // This was just a safeguard in the C implementation:
+ // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 + salt_string.length() + 1 + 86 + 1;
+
+ if (blocksize == 32) {
+ B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer);
+ B64.b64from24bit(altResult[21], altResult[1], altResult[11], 4, buffer);
+ B64.b64from24bit(altResult[12], altResult[22], altResult[2], 4, buffer);
+ B64.b64from24bit(altResult[3], altResult[13], altResult[23], 4, buffer);
+ B64.b64from24bit(altResult[24], altResult[4], altResult[14], 4, buffer);
+ B64.b64from24bit(altResult[15], altResult[25], altResult[5], 4, buffer);
+ B64.b64from24bit(altResult[6], altResult[16], altResult[26], 4, buffer);
+ B64.b64from24bit(altResult[27], altResult[7], altResult[17], 4, buffer);
+ B64.b64from24bit(altResult[18], altResult[28], altResult[8], 4, buffer);
+ B64.b64from24bit(altResult[9], altResult[19], altResult[29], 4, buffer);
+ B64.b64from24bit((byte) 0, altResult[31], altResult[30], 3, buffer);
+ } else {
+ B64.b64from24bit(altResult[0], altResult[21], altResult[42], 4, buffer);
+ B64.b64from24bit(altResult[22], altResult[43], altResult[1], 4, buffer);
+ B64.b64from24bit(altResult[44], altResult[2], altResult[23], 4, buffer);
+ B64.b64from24bit(altResult[3], altResult[24], altResult[45], 4, buffer);
+ B64.b64from24bit(altResult[25], altResult[46], altResult[4], 4, buffer);
+ B64.b64from24bit(altResult[47], altResult[5], altResult[26], 4, buffer);
+ B64.b64from24bit(altResult[6], altResult[27], altResult[48], 4, buffer);
+ B64.b64from24bit(altResult[28], altResult[49], altResult[7], 4, buffer);
+ B64.b64from24bit(altResult[50], altResult[8], altResult[29], 4, buffer);
+ B64.b64from24bit(altResult[9], altResult[30], altResult[51], 4, buffer);
+ B64.b64from24bit(altResult[31], altResult[52], altResult[10], 4, buffer);
+ B64.b64from24bit(altResult[53], altResult[11], altResult[32], 4, buffer);
+ B64.b64from24bit(altResult[12], altResult[33], altResult[54], 4, buffer);
+ B64.b64from24bit(altResult[34], altResult[55], altResult[13], 4, buffer);
+ B64.b64from24bit(altResult[56], altResult[14], altResult[35], 4, buffer);
+ B64.b64from24bit(altResult[15], altResult[36], altResult[57], 4, buffer);
+ B64.b64from24bit(altResult[37], altResult[58], altResult[16], 4, buffer);
+ B64.b64from24bit(altResult[59], altResult[17], altResult[38], 4, buffer);
+ B64.b64from24bit(altResult[18], altResult[39], altResult[60], 4, buffer);
+ B64.b64from24bit(altResult[40], altResult[61], altResult[19], 4, buffer);
+ B64.b64from24bit(altResult[62], altResult[20], altResult[41], 4, buffer);
+ B64.b64from24bit((byte) 0, (byte) 0, altResult[63], 2, buffer);
+ }
+
+ /*
+ * Clear the buffer for the intermediate result so that people attaching to processes or reading core dumps
+ * cannot get any information.
+ */
+ // Is there a better way to do this with the JVM?
+ Arrays.fill(tempResult, (byte) 0);
+ Arrays.fill(pBytes, (byte) 0);
+ Arrays.fill(sBytes, (byte) 0);
+ ctx.reset();
+ altCtx.reset();
+ Arrays.fill(keyBytes, (byte) 0);
+ Arrays.fill(saltBytes, (byte) 0);
+
+ return buffer.toString();
+ }
+
+ /**
+ * Generates a libc crypt() compatible "$6$" hash value with random salt.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext to hash
+ * @return complete hash value
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String sha512Crypt(final byte[] keyBytes) {
+ return sha512Crypt(keyBytes, null);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$6$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * plaintext to hash
+ * @param salt
+ * real salt value without prefix or "rounds="
+ * @return complete hash value including salt
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ * @throws RuntimeException
+ * when a {@link java.security.NoSuchAlgorithmException} is caught.
+ */
+ public static String sha512Crypt(final byte[] keyBytes, String salt) {
+ if (salt == null) {
+ salt = SHA512_PREFIX + B64.getRandomSalt(8);
+ }
+ return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE, MessageDigestAlgorithms.SHA_512);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java b/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java
new file mode 100644
index 00000000..151d9c89
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java
@@ -0,0 +1,413 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.util.Random;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * Unix crypt(3) algorithm implementation.
+ *
+ * This class only implements the traditional 56 bit DES based algorithm. Please use DigestUtils.crypt() for a method
+ * that distinguishes between all the algorithms supported in the current glibc's crypt().
+ *
+ * The Java implementation was taken from the JetSpeed Portal project (see
+ * org.apache.jetspeed.services.security.ldap.UnixCrypt).
+ *
+ * This class is slightly incompatible if the given salt contains characters that are not part of the allowed range
+ * [a-zA-Z0-9./].
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+public class UnixCrypt {
+
+ private static final int CON_SALT[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 35, 36, 37, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0 };
+
+ private static final int COV2CHAR[] = { 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 };
+
+ private static final char SALT_CHARS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
+ .toCharArray();
+
+ private static final boolean SHIFT2[] = { false, false, true, true, true, true, true, true, false, true, true,
+ true, true, true, true, false };
+
+ private static final int SKB[][] = {
+ { 0, 16, 0x20000000, 0x20000010, 0x10000, 0x10010, 0x20010000, 0x20010010, 2048, 2064, 0x20000800,
+ 0x20000810, 0x10800, 0x10810, 0x20010800, 0x20010810, 32, 48, 0x20000020, 0x20000030, 0x10020,
+ 0x10030, 0x20010020, 0x20010030, 2080, 2096, 0x20000820, 0x20000830, 0x10820, 0x10830, 0x20010820,
+ 0x20010830, 0x80000, 0x80010, 0x20080000, 0x20080010, 0x90000, 0x90010, 0x20090000, 0x20090010,
+ 0x80800, 0x80810, 0x20080800, 0x20080810, 0x90800, 0x90810, 0x20090800, 0x20090810, 0x80020,
+ 0x80030, 0x20080020, 0x20080030, 0x90020, 0x90030, 0x20090020, 0x20090030, 0x80820, 0x80830,
+ 0x20080820, 0x20080830, 0x90820, 0x90830, 0x20090820, 0x20090830 },
+ { 0, 0x2000000, 8192, 0x2002000, 0x200000, 0x2200000, 0x202000, 0x2202000, 4, 0x2000004, 8196, 0x2002004,
+ 0x200004, 0x2200004, 0x202004, 0x2202004, 1024, 0x2000400, 9216, 0x2002400, 0x200400, 0x2200400,
+ 0x202400, 0x2202400, 1028, 0x2000404, 9220, 0x2002404, 0x200404, 0x2200404, 0x202404, 0x2202404,
+ 0x10000000, 0x12000000, 0x10002000, 0x12002000, 0x10200000, 0x12200000, 0x10202000, 0x12202000,
+ 0x10000004, 0x12000004, 0x10002004, 0x12002004, 0x10200004, 0x12200004, 0x10202004, 0x12202004,
+ 0x10000400, 0x12000400, 0x10002400, 0x12002400, 0x10200400, 0x12200400, 0x10202400, 0x12202400,
+ 0x10000404, 0x12000404, 0x10002404, 0x12002404, 0x10200404, 0x12200404, 0x10202404, 0x12202404 },
+ { 0, 1, 0x40000, 0x40001, 0x1000000, 0x1000001, 0x1040000, 0x1040001, 2, 3, 0x40002, 0x40003, 0x1000002,
+ 0x1000003, 0x1040002, 0x1040003, 512, 513, 0x40200, 0x40201, 0x1000200, 0x1000201, 0x1040200,
+ 0x1040201, 514, 515, 0x40202, 0x40203, 0x1000202, 0x1000203, 0x1040202, 0x1040203, 0x8000000,
+ 0x8000001, 0x8040000, 0x8040001, 0x9000000, 0x9000001, 0x9040000, 0x9040001, 0x8000002, 0x8000003,
+ 0x8040002, 0x8040003, 0x9000002, 0x9000003, 0x9040002, 0x9040003, 0x8000200, 0x8000201, 0x8040200,
+ 0x8040201, 0x9000200, 0x9000201, 0x9040200, 0x9040201, 0x8000202, 0x8000203, 0x8040202, 0x8040203,
+ 0x9000202, 0x9000203, 0x9040202, 0x9040203 },
+ { 0, 0x100000, 256, 0x100100, 8, 0x100008, 264, 0x100108, 4096, 0x101000, 4352, 0x101100, 4104, 0x101008,
+ 4360, 0x101108, 0x4000000, 0x4100000, 0x4000100, 0x4100100, 0x4000008, 0x4100008, 0x4000108,
+ 0x4100108, 0x4001000, 0x4101000, 0x4001100, 0x4101100, 0x4001008, 0x4101008, 0x4001108, 0x4101108,
+ 0x20000, 0x120000, 0x20100, 0x120100, 0x20008, 0x120008, 0x20108, 0x120108, 0x21000, 0x121000,
+ 0x21100, 0x121100, 0x21008, 0x121008, 0x21108, 0x121108, 0x4020000, 0x4120000, 0x4020100,
+ 0x4120100, 0x4020008, 0x4120008, 0x4020108, 0x4120108, 0x4021000, 0x4121000, 0x4021100, 0x4121100,
+ 0x4021008, 0x4121008, 0x4021108, 0x4121108 },
+ { 0, 0x10000000, 0x10000, 0x10010000, 4, 0x10000004, 0x10004, 0x10010004, 0x20000000, 0x30000000,
+ 0x20010000, 0x30010000, 0x20000004, 0x30000004, 0x20010004, 0x30010004, 0x100000, 0x10100000,
+ 0x110000, 0x10110000, 0x100004, 0x10100004, 0x110004, 0x10110004, 0x20100000, 0x30100000,
+ 0x20110000, 0x30110000, 0x20100004, 0x30100004, 0x20110004, 0x30110004, 4096, 0x10001000, 0x11000,
+ 0x10011000, 4100, 0x10001004, 0x11004, 0x10011004, 0x20001000, 0x30001000, 0x20011000, 0x30011000,
+ 0x20001004, 0x30001004, 0x20011004, 0x30011004, 0x101000, 0x10101000, 0x111000, 0x10111000,
+ 0x101004, 0x10101004, 0x111004, 0x10111004, 0x20101000, 0x30101000, 0x20111000, 0x30111000,
+ 0x20101004, 0x30101004, 0x20111004, 0x30111004 },
+ { 0, 0x8000000, 8, 0x8000008, 1024, 0x8000400, 1032, 0x8000408, 0x20000, 0x8020000, 0x20008, 0x8020008,
+ 0x20400, 0x8020400, 0x20408, 0x8020408, 1, 0x8000001, 9, 0x8000009, 1025, 0x8000401, 1033,
+ 0x8000409, 0x20001, 0x8020001, 0x20009, 0x8020009, 0x20401, 0x8020401, 0x20409, 0x8020409,
+ 0x2000000, 0xa000000, 0x2000008, 0xa000008, 0x2000400, 0xa000400, 0x2000408, 0xa000408, 0x2020000,
+ 0xa020000, 0x2020008, 0xa020008, 0x2020400, 0xa020400, 0x2020408, 0xa020408, 0x2000001, 0xa000001,
+ 0x2000009, 0xa000009, 0x2000401, 0xa000401, 0x2000409, 0xa000409, 0x2020001, 0xa020001, 0x2020009,
+ 0xa020009, 0x2020401, 0xa020401, 0x2020409, 0xa020409 },
+ { 0, 256, 0x80000, 0x80100, 0x1000000, 0x1000100, 0x1080000, 0x1080100, 16, 272, 0x80010, 0x80110,
+ 0x1000010, 0x1000110, 0x1080010, 0x1080110, 0x200000, 0x200100, 0x280000, 0x280100, 0x1200000,
+ 0x1200100, 0x1280000, 0x1280100, 0x200010, 0x200110, 0x280010, 0x280110, 0x1200010, 0x1200110,
+ 0x1280010, 0x1280110, 512, 768, 0x80200, 0x80300, 0x1000200, 0x1000300, 0x1080200, 0x1080300, 528,
+ 784, 0x80210, 0x80310, 0x1000210, 0x1000310, 0x1080210, 0x1080310, 0x200200, 0x200300, 0x280200,
+ 0x280300, 0x1200200, 0x1200300, 0x1280200, 0x1280300, 0x200210, 0x200310, 0x280210, 0x280310,
+ 0x1200210, 0x1200310, 0x1280210, 0x1280310 },
+ { 0, 0x4000000, 0x40000, 0x4040000, 2, 0x4000002, 0x40002, 0x4040002, 8192, 0x4002000, 0x42000, 0x4042000,
+ 8194, 0x4002002, 0x42002, 0x4042002, 32, 0x4000020, 0x40020, 0x4040020, 34, 0x4000022, 0x40022,
+ 0x4040022, 8224, 0x4002020, 0x42020, 0x4042020, 8226, 0x4002022, 0x42022, 0x4042022, 2048,
+ 0x4000800, 0x40800, 0x4040800, 2050, 0x4000802, 0x40802, 0x4040802, 10240, 0x4002800, 0x42800,
+ 0x4042800, 10242, 0x4002802, 0x42802, 0x4042802, 2080, 0x4000820, 0x40820, 0x4040820, 2082,
+ 0x4000822, 0x40822, 0x4040822, 10272, 0x4002820, 0x42820, 0x4042820, 10274, 0x4002822, 0x42822,
+ 0x4042822 } };
+
+ private static final int SPTRANS[][] = {
+ { 0x820200, 0x20000, 0x80800000, 0x80820200, 0x800000, 0x80020200, 0x80020000, 0x80800000, 0x80020200,
+ 0x820200, 0x820000, 0x80000200, 0x80800200, 0x800000, 0, 0x80020000, 0x20000, 0x80000000,
+ 0x800200, 0x20200, 0x80820200, 0x820000, 0x80000200, 0x800200, 0x80000000, 512, 0x20200,
+ 0x80820000, 512, 0x80800200, 0x80820000, 0, 0, 0x80820200, 0x800200, 0x80020000, 0x820200,
+ 0x20000, 0x80000200, 0x800200, 0x80820000, 512, 0x20200, 0x80800000, 0x80020200, 0x80000000,
+ 0x80800000, 0x820000, 0x80820200, 0x20200, 0x820000, 0x80800200, 0x800000, 0x80000200, 0x80020000,
+ 0, 0x20000, 0x800000, 0x80800200, 0x820200, 0x80000000, 0x80820000, 512, 0x80020200 },
+ { 0x10042004, 0, 0x42000, 0x10040000, 0x10000004, 8196, 0x10002000, 0x42000, 8192, 0x10040004, 4,
+ 0x10002000, 0x40004, 0x10042000, 0x10040000, 4, 0x40000, 0x10002004, 0x10040004, 8192, 0x42004,
+ 0x10000000, 0, 0x40004, 0x10002004, 0x42004, 0x10042000, 0x10000004, 0x10000000, 0x40000, 8196,
+ 0x10042004, 0x40004, 0x10042000, 0x10002000, 0x42004, 0x10042004, 0x40004, 0x10000004, 0,
+ 0x10000000, 8196, 0x40000, 0x10040004, 8192, 0x10000000, 0x42004, 0x10002004, 0x10042000, 8192, 0,
+ 0x10000004, 4, 0x10042004, 0x42000, 0x10040000, 0x10040004, 0x40000, 8196, 0x10002000, 0x10002004,
+ 4, 0x10040000, 0x42000 },
+ { 0x41000000, 0x1010040, 64, 0x41000040, 0x40010000, 0x1000000, 0x41000040, 0x10040, 0x1000040, 0x10000,
+ 0x1010000, 0x40000000, 0x41010040, 0x40000040, 0x40000000, 0x41010000, 0, 0x40010000, 0x1010040,
+ 64, 0x40000040, 0x41010040, 0x10000, 0x41000000, 0x41010000, 0x1000040, 0x40010040, 0x1010000,
+ 0x10040, 0, 0x1000000, 0x40010040, 0x1010040, 64, 0x40000000, 0x10000, 0x40000040, 0x40010000,
+ 0x1010000, 0x41000040, 0, 0x1010040, 0x10040, 0x41010000, 0x40010000, 0x1000000, 0x41010040,
+ 0x40000000, 0x40010040, 0x41000000, 0x1000000, 0x41010040, 0x10000, 0x1000040, 0x41000040,
+ 0x10040, 0x1000040, 0, 0x41010000, 0x40000040, 0x41000000, 0x40010040, 64, 0x1010000 },
+ { 0x100402, 0x4000400, 2, 0x4100402, 0, 0x4100000, 0x4000402, 0x100002, 0x4100400, 0x4000002, 0x4000000,
+ 1026, 0x4000002, 0x100402, 0x100000, 0x4000000, 0x4100002, 0x100400, 1024, 2, 0x100400, 0x4000402,
+ 0x4100000, 1024, 1026, 0, 0x100002, 0x4100400, 0x4000400, 0x4100002, 0x4100402, 0x100000,
+ 0x4100002, 1026, 0x100000, 0x4000002, 0x100400, 0x4000400, 2, 0x4100000, 0x4000402, 0, 1024,
+ 0x100002, 0, 0x4100002, 0x4100400, 1024, 0x4000000, 0x4100402, 0x100402, 0x100000, 0x4100402, 2,
+ 0x4000400, 0x100402, 0x100002, 0x100400, 0x4100000, 0x4000402, 1026, 0x4000000, 0x4000002,
+ 0x4100400 },
+ { 0x2000000, 16384, 256, 0x2004108, 0x2004008, 0x2000100, 16648, 0x2004000, 16384, 8, 0x2000008, 16640,
+ 0x2000108, 0x2004008, 0x2004100, 0, 16640, 0x2000000, 16392, 264, 0x2000100, 16648, 0, 0x2000008,
+ 8, 0x2000108, 0x2004108, 16392, 0x2004000, 256, 264, 0x2004100, 0x2004100, 0x2000108, 16392,
+ 0x2004000, 16384, 8, 0x2000008, 0x2000100, 0x2000000, 16640, 0x2004108, 0, 16648, 0x2000000, 256,
+ 16392, 0x2000108, 256, 0, 0x2004108, 0x2004008, 0x2004100, 264, 16384, 16640, 0x2004008,
+ 0x2000100, 264, 8, 16648, 0x2004000, 0x2000008 },
+ { 0x20000010, 0x80010, 0, 0x20080800, 0x80010, 2048, 0x20000810, 0x80000, 2064, 0x20080810, 0x80800,
+ 0x20000000, 0x20000800, 0x20000010, 0x20080000, 0x80810, 0x80000, 0x20000810, 0x20080010, 0, 2048,
+ 16, 0x20080800, 0x20080010, 0x20080810, 0x20080000, 0x20000000, 2064, 16, 0x80800, 0x80810,
+ 0x20000800, 2064, 0x20000000, 0x20000800, 0x80810, 0x20080800, 0x80010, 0, 0x20000800, 0x20000000,
+ 2048, 0x20080010, 0x80000, 0x80010, 0x20080810, 0x80800, 16, 0x20080810, 0x80800, 0x80000,
+ 0x20000810, 0x20000010, 0x20080000, 0x80810, 0, 2048, 0x20000010, 0x20000810, 0x20080800,
+ 0x20080000, 2064, 16, 0x20080010 },
+ { 4096, 128, 0x400080, 0x400001, 0x401081, 4097, 4224, 0, 0x400000, 0x400081, 129, 0x401000, 1, 0x401080,
+ 0x401000, 129, 0x400081, 4096, 4097, 0x401081, 0, 0x400080, 0x400001, 4224, 0x401001, 4225,
+ 0x401080, 1, 4225, 0x401001, 128, 0x400000, 4225, 0x401000, 0x401001, 129, 4096, 128, 0x400000,
+ 0x401001, 0x400081, 4225, 4224, 0, 128, 0x400001, 1, 0x400080, 0, 0x400081, 0x400080, 4224, 129,
+ 4096, 0x401081, 0x400000, 0x401080, 1, 4097, 0x401081, 0x400001, 0x401080, 0x401000, 4097 },
+ { 0x8200020, 0x8208000, 32800, 0, 0x8008000, 0x200020, 0x8200000, 0x8208020, 32, 0x8000000, 0x208000,
+ 32800, 0x208020, 0x8008020, 0x8000020, 0x8200000, 32768, 0x208020, 0x200020, 0x8008000, 0x8208020,
+ 0x8000020, 0, 0x208000, 0x8000000, 0x200000, 0x8008020, 0x8200020, 0x200000, 32768, 0x8208000, 32,
+ 0x200000, 32768, 0x8000020, 0x8208020, 32800, 0x8000000, 0, 0x208000, 0x8200020, 0x8008020,
+ 0x8008000, 0x200020, 0x8208000, 32, 0x200020, 0x8008000, 0x8208020, 0x200000, 0x8200000,
+ 0x8000020, 0x208000, 32800, 0x8008020, 0x8200000, 32, 0x8208000, 0x208020, 0, 0x8000000,
+ 0x8200020, 32768, 0x208020 } };
+
+ /**
+ * Generates a crypt(3) compatible hash using the DES algorithm.
+ *
+ * As no salt is given, a random one will be used.
+ *
+ * @param original
+ * plaintext password
+ * @return a 13 character string starting with the salt string
+ */
+ public static String crypt(final byte[] original) {
+ return crypt(original, null);
+ }
+
+ /**
+ * Generates a crypt(3) compatible hash using the DES algorithm.
+ *
+ * Using unspecified characters as salt results incompatible hash values.
+ *
+ * @param original
+ * plaintext password
+ * @param salt
+ * a two character string drawn from [a-zA-Z0-9./] or null for a random one
+ * @return a 13 character string starting with the salt string
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ */
+ public static String crypt(final byte[] original, String salt) {
+ if (salt == null) {
+ final Random randomGenerator = new Random();
+ final int numSaltChars = SALT_CHARS.length;
+ salt = "" + SALT_CHARS[randomGenerator.nextInt(numSaltChars)] +
+ SALT_CHARS[randomGenerator.nextInt(numSaltChars)];
+ } else if (!salt.matches("^[" + B64.B64T + "]{2,}$")) {
+ throw new IllegalArgumentException("Invalid salt value: " + salt);
+ }
+
+ final StringBuilder buffer = new StringBuilder(" ");
+ final char charZero = salt.charAt(0);
+ final char charOne = salt.charAt(1);
+ buffer.setCharAt(0, charZero);
+ buffer.setCharAt(1, charOne);
+ final int eSwap0 = CON_SALT[charZero];
+ final int eSwap1 = CON_SALT[charOne] << 4;
+ final byte key[] = new byte[8];
+ for (int i = 0; i < key.length; i++) {
+ key[i] = 0;
+ }
+
+ for (int i = 0; i < key.length && i < original.length; i++) {
+ final int iChar = original[i];
+ key[i] = (byte) (iChar << 1);
+ }
+
+ final int schedule[] = desSetKey(key);
+ final int out[] = body(schedule, eSwap0, eSwap1);
+ final byte b[] = new byte[9];
+ intToFourBytes(out[0], b, 0);
+ intToFourBytes(out[1], b, 4);
+ b[8] = 0;
+ int i = 2;
+ int y = 0;
+ int u = 128;
+ for (; i < 13; i++) {
+ int j = 0;
+ int c = 0;
+ for (; j < 6; j++) {
+ c <<= 1;
+ if ((b[y] & u) != 0) {
+ c |= 0x1;
+ }
+ u >>>= 1;
+ if (u == 0) {
+ y++;
+ u = 128;
+ }
+ buffer.setCharAt(i, (char) COV2CHAR[c]);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Generates a crypt(3) compatible hash using the DES algorithm.
+ *
+ * As no salt is given, a random one is used.
+ *
+ * @param original
+ * plaintext password
+ * @return a 13 character string starting with the salt string
+ */
+ public static String crypt(final String original) {
+ return crypt(original.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Generates a crypt(3) compatible hash using the DES algorithm.
+ *
+ * @param original
+ * plaintext password
+ * @param salt
+ * a two character string drawn from [a-zA-Z0-9./] or null for a random one
+ * @return a 13 character string starting with the salt string
+ * @throws IllegalArgumentException
+ * if the salt does not match the allowed pattern
+ */
+ public static String crypt(final String original, final String salt) {
+ return crypt(original.getBytes(Charsets.UTF_8), salt);
+ }
+
+ private static int[] body(final int schedule[], final int eSwap0, final int eSwap1) {
+ int left = 0;
+ int right = 0;
+ int t = 0;
+ for (int j = 0; j < 25; j++) {
+ for (int i = 0; i < 32; i += 4) {
+ left = dEncrypt(left, right, i, eSwap0, eSwap1, schedule);
+ right = dEncrypt(right, left, i + 2, eSwap0, eSwap1, schedule);
+ }
+ t = left;
+ left = right;
+ right = t;
+ }
+
+ t = right;
+ right = left >>> 1 | left << 31;
+ left = t >>> 1 | t << 31;
+ final int results[] = new int[2];
+ permOp(right, left, 1, 0x55555555, results);
+ right = results[0];
+ left = results[1];
+ permOp(left, right, 8, 0xff00ff, results);
+ left = results[0];
+ right = results[1];
+ permOp(right, left, 2, 0x33333333, results);
+ right = results[0];
+ left = results[1];
+ permOp(left, right, 16, 65535, results);
+ left = results[0];
+ right = results[1];
+ permOp(right, left, 4, 0xf0f0f0f, results);
+ right = results[0];
+ left = results[1];
+ final int out[] = new int[2];
+ out[0] = left;
+ out[1] = right;
+ return out;
+ }
+
+ private static int byteToUnsigned(final byte b) {
+ final int value = b;
+ return value < 0 ? value + 256 : value;
+ }
+
+ private static int dEncrypt(int el, final int r, final int s, final int e0, final int e1, final int sArr[]) {
+ int v = r ^ r >>> 16;
+ int u = v & e0;
+ v &= e1;
+ u = u ^ u << 16 ^ r ^ sArr[s];
+ int t = v ^ v << 16 ^ r ^ sArr[s + 1];
+ t = t >>> 4 | t << 28;
+ el ^= SPTRANS[1][t & 0x3f] | SPTRANS[3][t >>> 8 & 0x3f] | SPTRANS[5][t >>> 16 & 0x3f] |
+ SPTRANS[7][t >>> 24 & 0x3f] | SPTRANS[0][u & 0x3f] | SPTRANS[2][u >>> 8 & 0x3f] |
+ SPTRANS[4][u >>> 16 & 0x3f] | SPTRANS[6][u >>> 24 & 0x3f];
+ return el;
+ }
+
+ private static int[] desSetKey(final byte key[]) {
+ final int schedule[] = new int[32];
+ int c = fourBytesToInt(key, 0);
+ int d = fourBytesToInt(key, 4);
+ final int results[] = new int[2];
+ permOp(d, c, 4, 0xf0f0f0f, results);
+ d = results[0];
+ c = results[1];
+ c = hPermOp(c, -2, 0xcccc0000);
+ d = hPermOp(d, -2, 0xcccc0000);
+ permOp(d, c, 1, 0x55555555, results);
+ d = results[0];
+ c = results[1];
+ permOp(c, d, 8, 0xff00ff, results);
+ c = results[0];
+ d = results[1];
+ permOp(d, c, 1, 0x55555555, results);
+ d = results[0];
+ c = results[1];
+ d = (d & 0xff) << 16 | d & 0xff00 | (d & 0xff0000) >>> 16 | (c & 0xf0000000) >>> 4;
+ c &= 0xfffffff;
+ int j = 0;
+ for (int i = 0; i < 16; i++) {
+ if (SHIFT2[i]) {
+ c = c >>> 2 | c << 26;
+ d = d >>> 2 | d << 26;
+ } else {
+ c = c >>> 1 | c << 27;
+ d = d >>> 1 | d << 27;
+ }
+ c &= 0xfffffff;
+ d &= 0xfffffff;
+ int s = SKB[0][c & 0x3f] | SKB[1][c >>> 6 & 0x3 | c >>> 7 & 0x3c] |
+ SKB[2][c >>> 13 & 0xf | c >>> 14 & 0x30] |
+ SKB[3][c >>> 20 & 0x1 | c >>> 21 & 0x6 | c >>> 22 & 0x38];
+ final int t = SKB[4][d & 0x3f] | SKB[5][d >>> 7 & 0x3 | d >>> 8 & 0x3c] | SKB[6][d >>> 15 & 0x3f] |
+ SKB[7][d >>> 21 & 0xf | d >>> 22 & 0x30];
+ schedule[j++] = (t << 16 | s & 0xffff);
+ s = s >>> 16 | t & 0xffff0000;
+ s = s << 4 | s >>> 28;
+ schedule[j++] = s;
+ }
+
+ return schedule;
+ }
+
+ private static int fourBytesToInt(final byte b[], int offset) {
+ int value = byteToUnsigned(b[offset++]);
+ value |= byteToUnsigned(b[offset++]) << 8;
+ value |= byteToUnsigned(b[offset++]) << 16;
+ value |= byteToUnsigned(b[offset++]) << 24;
+ return value;
+ }
+
+ private static int hPermOp(int a, final int n, final int m) {
+ final int t = (a << 16 - n ^ a) & m;
+ a = a ^ t ^ t >>> 16 - n;
+ return a;
+ }
+
+ private static void intToFourBytes(final int iValue, final byte b[], int offset) {
+ b[offset++] = (byte) (iValue & 0xff);
+ b[offset++] = (byte) (iValue >>> 8 & 0xff);
+ b[offset++] = (byte) (iValue >>> 16 & 0xff);
+ b[offset++] = (byte) (iValue >>> 24 & 0xff);
+ }
+
+ private static void permOp(int a, int b, final int n, final int m, final int results[]) {
+ final int t = (a >>> n ^ b) & m;
+ a ^= t << n;
+ b ^= t;
+ results[0] = a;
+ results[1] = b;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/digest/package.html b/src/main/java/org/apache/commons/codec/digest/package.html
new file mode 100644
index 00000000..22cceb4c
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/digest/package.html
@@ -0,0 +1,24 @@
+
+
+
+ Simplifies common {@link java.security.MessageDigest} tasks and
+ includes a libc crypt(3) compatible crypt method that supports DES,
+ MD5, SHA-256 and SHA-512 based algorithms as well as the Apache
+ specific "$apr1$" variant.
+
+
diff --git a/src/main/java/org/apache/commons/codec/language/AbstractCaverphone.java b/src/main/java/org/apache/commons/codec/language/AbstractCaverphone.java
new file mode 100644
index 00000000..6a38d630
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/AbstractCaverphone.java
@@ -0,0 +1,80 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Caverphone value.
+ *
+ * This is an algorithm created by the Caversham Project at the University of Otago. It implements the Caverphone 2.0
+ * algorithm:
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id: Caverphone.java 1075947 2011-03-01 17:56:14Z ggregory $
+ * @see Wikipedia - Caverphone
+ * @since 1.5
+ */
+public abstract class AbstractCaverphone implements StringEncoder {
+
+ /**
+ * Creates an instance of the Caverphone encoder
+ */
+ public AbstractCaverphone() {
+ super();
+ }
+
+ /**
+ * Encodes an Object using the caverphone algorithm. This method is provided in order to satisfy the requirements of
+ * the Encoder interface, and will throw an EncoderException if the supplied object is not of type java.lang.String.
+ *
+ * @param source
+ * Object to encode
+ * @return An object (or type java.lang.String) containing the caverphone code which corresponds to the String
+ * supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ */
+ @Override
+ public Object encode(final Object source) throws EncoderException {
+ if (!(source instanceof String)) {
+ throw new EncoderException("Parameter supplied to Caverphone encode is not of type java.lang.String");
+ }
+ return this.encode((String) source);
+ }
+
+ /**
+ * Tests if the encodings of two strings are equal.
+ *
+ * This method might be promoted to a new AbstractStringEncoder superclass.
+ *
+ * @param str1
+ * First of two strings to compare
+ * @param str2
+ * Second of two strings to compare
+ * @return true
if the encodings of these strings are identical, false
otherwise.
+ * @throws EncoderException
+ * thrown if there is an error condition during the encoding process.
+ */
+ public boolean isEncodeEqual(final String str1, final String str2) throws EncoderException {
+ return this.encode(str1).equals(this.encode(str2));
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Caverphone.java b/src/main/java/org/apache/commons/codec/language/Caverphone.java
new file mode 100644
index 00000000..42c96730
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Caverphone.java
@@ -0,0 +1,105 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Caverphone 2.0 value. Delegate to a {@link Caverphone2} instance.
+ *
+ * This is an algorithm created by the Caversham Project at the University of Otago. It implements the Caverphone 2.0
+ * algorithm:
+ *
+ * @version $Id: Caverphone.java 1079535 2011-03-08 20:54:37Z ggregory $
+ * @see Wikipedia - Caverphone
+ * @see Caverphone 2.0 specification
+ * @since 1.4
+ * @deprecated 1.5 Replaced by {@link Caverphone2}, will be removed in 2.0.
+ */
+@Deprecated
+public class Caverphone implements StringEncoder {
+
+ /**
+ * Delegate to a {@link Caverphone2} instance to avoid code duplication.
+ */
+ final private Caverphone2 encoder = new Caverphone2();
+
+ /**
+ * Creates an instance of the Caverphone encoder
+ */
+ public Caverphone() {
+ super();
+ }
+
+ /**
+ * Encodes the given String into a Caverphone value.
+ *
+ * @param source
+ * String the source string
+ * @return A caverphone code for the given String
+ */
+ public String caverphone(final String source) {
+ return this.encoder.encode(source);
+ }
+
+ /**
+ * Encodes an Object using the caverphone algorithm. This method is provided in order to satisfy the requirements of
+ * the Encoder interface, and will throw an EncoderException if the supplied object is not of type java.lang.String.
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (or type java.lang.String) containing the caverphone code which corresponds to the String
+ * supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("Parameter supplied to Caverphone encode is not of type java.lang.String");
+ }
+ return this.caverphone((String) obj);
+ }
+
+ /**
+ * Encodes a String using the Caverphone algorithm.
+ *
+ * @param str
+ * String object to encode
+ * @return The caverphone code corresponding to the String supplied
+ */
+ @Override
+ public String encode(final String str) {
+ return this.caverphone(str);
+ }
+
+ /**
+ * Tests if the caverphones of two strings are identical.
+ *
+ * @param str1
+ * First of two strings to compare
+ * @param str2
+ * Second of two strings to compare
+ * @return true
if the caverphones of these strings are identical, false
otherwise.
+ */
+ public boolean isCaverphoneEqual(final String str1, final String str2) {
+ return this.caverphone(str1).equals(this.caverphone(str2));
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Caverphone1.java b/src/main/java/org/apache/commons/codec/language/Caverphone1.java
new file mode 100644
index 00000000..6b8a312a
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Caverphone1.java
@@ -0,0 +1,127 @@
+/*
+ * 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.commons.codec.language;
+
+/**
+ * Encodes a string into a Caverphone 1.0 value.
+ *
+ * This is an algorithm created by the Caversham Project at the University of Otago. It implements the Caverphone 1.0
+ * algorithm:
+ *
+ * @version $Id: Caverphone.java 1075947 2011-03-01 17:56:14Z ggregory $
+ * @see Wikipedia - Caverphone
+ * @see Caverphone 1.0 specification
+ * @since 1.5
+ *
+ * This class is immutable and thread-safe.
+ */
+public class Caverphone1 extends AbstractCaverphone {
+
+ private static final String SIX_1 = "111111";
+
+ /**
+ * Encodes the given String into a Caverphone value.
+ *
+ * @param source
+ * String the source string
+ * @return A caverphone code for the given String
+ */
+ @Override
+ public String encode(final String source) {
+ String txt = source;
+ if (txt == null || txt.length() == 0) {
+ return SIX_1;
+ }
+
+ // 1. Convert to lowercase
+ txt = txt.toLowerCase(java.util.Locale.ENGLISH);
+
+ // 2. Remove anything not A-Z
+ txt = txt.replaceAll("[^a-z]", "");
+
+ // 3. Handle various start options
+ // 2 is a temporary placeholder to indicate a consonant which we are no longer interested in.
+ txt = txt.replaceAll("^cough", "cou2f");
+ txt = txt.replaceAll("^rough", "rou2f");
+ txt = txt.replaceAll("^tough", "tou2f");
+ txt = txt.replaceAll("^enough", "enou2f");
+ txt = txt.replaceAll("^gn", "2n");
+
+ // End
+ txt = txt.replaceAll("mb$", "m2");
+
+ // 4. Handle replacements
+ txt = txt.replaceAll("cq", "2q");
+ txt = txt.replaceAll("ci", "si");
+ txt = txt.replaceAll("ce", "se");
+ txt = txt.replaceAll("cy", "sy");
+ txt = txt.replaceAll("tch", "2ch");
+ txt = txt.replaceAll("c", "k");
+ txt = txt.replaceAll("q", "k");
+ txt = txt.replaceAll("x", "k");
+ txt = txt.replaceAll("v", "f");
+ txt = txt.replaceAll("dg", "2g");
+ txt = txt.replaceAll("tio", "sio");
+ txt = txt.replaceAll("tia", "sia");
+ txt = txt.replaceAll("d", "t");
+ txt = txt.replaceAll("ph", "fh");
+ txt = txt.replaceAll("b", "p");
+ txt = txt.replaceAll("sh", "s2");
+ txt = txt.replaceAll("z", "s");
+ txt = txt.replaceAll("^[aeiou]", "A");
+ // 3 is a temporary placeholder marking a vowel
+ txt = txt.replaceAll("[aeiou]", "3");
+ txt = txt.replaceAll("3gh3", "3kh3");
+ txt = txt.replaceAll("gh", "22");
+ txt = txt.replaceAll("g", "k");
+ txt = txt.replaceAll("s+", "S");
+ txt = txt.replaceAll("t+", "T");
+ txt = txt.replaceAll("p+", "P");
+ txt = txt.replaceAll("k+", "K");
+ txt = txt.replaceAll("f+", "F");
+ txt = txt.replaceAll("m+", "M");
+ txt = txt.replaceAll("n+", "N");
+ txt = txt.replaceAll("w3", "W3");
+ txt = txt.replaceAll("wy", "Wy"); // 1.0 only
+ txt = txt.replaceAll("wh3", "Wh3");
+ txt = txt.replaceAll("why", "Why"); // 1.0 only
+ txt = txt.replaceAll("w", "2");
+ txt = txt.replaceAll("^h", "A");
+ txt = txt.replaceAll("h", "2");
+ txt = txt.replaceAll("r3", "R3");
+ txt = txt.replaceAll("ry", "Ry"); // 1.0 only
+ txt = txt.replaceAll("r", "2");
+ txt = txt.replaceAll("l3", "L3");
+ txt = txt.replaceAll("ly", "Ly"); // 1.0 only
+ txt = txt.replaceAll("l", "2");
+ txt = txt.replaceAll("j", "y"); // 1.0 only
+ txt = txt.replaceAll("y3", "Y3"); // 1.0 only
+ txt = txt.replaceAll("y", "2"); // 1.0 only
+
+ // 5. Handle removals
+ txt = txt.replaceAll("2", "");
+ txt = txt.replaceAll("3", "");
+
+ // 6. put ten 1s on the end
+ txt = txt + SIX_1;
+
+ // 7. take the first six characters as the code
+ return txt.substring(0, SIX_1.length());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Caverphone2.java b/src/main/java/org/apache/commons/codec/language/Caverphone2.java
new file mode 100644
index 00000000..624e3958
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Caverphone2.java
@@ -0,0 +1,131 @@
+/*
+ * 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.commons.codec.language;
+
+/**
+ * Encodes a string into a Caverphone 2.0 value.
+ *
+ * This is an algorithm created by the Caversham Project at the University of Otago. It implements the Caverphone 2.0
+ * algorithm:
+ *
+ * @version $Id: Caverphone.java 1075947 2011-03-01 17:56:14Z ggregory $
+ * @see Wikipedia - Caverphone
+ * @see Caverphone 2.0 specification
+ * @since 1.5
+ *
+ * This class is immutable and thread-safe.
+ */
+public class Caverphone2 extends AbstractCaverphone {
+
+ private static final String TEN_1 = "1111111111";
+
+ /**
+ * Encodes the given String into a Caverphone 2.0 value.
+ *
+ * @param source
+ * String the source string
+ * @return A caverphone code for the given String
+ */
+ @Override
+ public String encode(final String source) {
+ String txt = source;
+ if (txt == null || txt.length() == 0) {
+ return TEN_1;
+ }
+
+ // 1. Convert to lowercase
+ txt = txt.toLowerCase(java.util.Locale.ENGLISH);
+
+ // 2. Remove anything not A-Z
+ txt = txt.replaceAll("[^a-z]", "");
+
+ // 2.5. Remove final e
+ txt = txt.replaceAll("e$", ""); // 2.0 only
+
+ // 3. Handle various start options
+ txt = txt.replaceAll("^cough", "cou2f");
+ txt = txt.replaceAll("^rough", "rou2f");
+ txt = txt.replaceAll("^tough", "tou2f");
+ txt = txt.replaceAll("^enough", "enou2f"); // 2.0 only
+ txt = txt.replaceAll("^trough", "trou2f"); // 2.0 only
+ // note the spec says ^enough here again, c+p error I assume
+ txt = txt.replaceAll("^gn", "2n");
+
+ // End
+ txt = txt.replaceAll("mb$", "m2");
+
+ // 4. Handle replacements
+ txt = txt.replaceAll("cq", "2q");
+ txt = txt.replaceAll("ci", "si");
+ txt = txt.replaceAll("ce", "se");
+ txt = txt.replaceAll("cy", "sy");
+ txt = txt.replaceAll("tch", "2ch");
+ txt = txt.replaceAll("c", "k");
+ txt = txt.replaceAll("q", "k");
+ txt = txt.replaceAll("x", "k");
+ txt = txt.replaceAll("v", "f");
+ txt = txt.replaceAll("dg", "2g");
+ txt = txt.replaceAll("tio", "sio");
+ txt = txt.replaceAll("tia", "sia");
+ txt = txt.replaceAll("d", "t");
+ txt = txt.replaceAll("ph", "fh");
+ txt = txt.replaceAll("b", "p");
+ txt = txt.replaceAll("sh", "s2");
+ txt = txt.replaceAll("z", "s");
+ txt = txt.replaceAll("^[aeiou]", "A");
+ txt = txt.replaceAll("[aeiou]", "3");
+ txt = txt.replaceAll("j", "y"); // 2.0 only
+ txt = txt.replaceAll("^y3", "Y3"); // 2.0 only
+ txt = txt.replaceAll("^y", "A"); // 2.0 only
+ txt = txt.replaceAll("y", "3"); // 2.0 only
+ txt = txt.replaceAll("3gh3", "3kh3");
+ txt = txt.replaceAll("gh", "22");
+ txt = txt.replaceAll("g", "k");
+ txt = txt.replaceAll("s+", "S");
+ txt = txt.replaceAll("t+", "T");
+ txt = txt.replaceAll("p+", "P");
+ txt = txt.replaceAll("k+", "K");
+ txt = txt.replaceAll("f+", "F");
+ txt = txt.replaceAll("m+", "M");
+ txt = txt.replaceAll("n+", "N");
+ txt = txt.replaceAll("w3", "W3");
+ txt = txt.replaceAll("wh3", "Wh3");
+ txt = txt.replaceAll("w$", "3"); // 2.0 only
+ txt = txt.replaceAll("w", "2");
+ txt = txt.replaceAll("^h", "A");
+ txt = txt.replaceAll("h", "2");
+ txt = txt.replaceAll("r3", "R3");
+ txt = txt.replaceAll("r$", "3"); // 2.0 only
+ txt = txt.replaceAll("r", "2");
+ txt = txt.replaceAll("l3", "L3");
+ txt = txt.replaceAll("l$", "3"); // 2.0 only
+ txt = txt.replaceAll("l", "2");
+
+ // 5. Handle removals
+ txt = txt.replaceAll("2", "");
+ txt = txt.replaceAll("3$", "A"); // 2.0 only
+ txt = txt.replaceAll("3", "");
+
+ // 6. put ten 1s on the end
+ txt = txt + TEN_1;
+
+ // 7. take the first ten characters as the code
+ return txt.substring(0, TEN_1.length());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/ColognePhonetic.java b/src/main/java/org/apache/commons/codec/language/ColognePhonetic.java
new file mode 100644
index 00000000..01f395c3
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/ColognePhonetic.java
@@ -0,0 +1,445 @@
+/*
+ * 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.commons.codec.language;
+
+import java.util.Locale;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Cologne Phonetic value.
+ *
+ * Implements the Kölner Phonetik (Cologne
+ * Phonetic) algorithm issued by Hans Joachim Postel in 1969.
+ *
+ *
+ * The Kölner Phonetik is a phonetic algorithm which is optimized for the German language. It is related to
+ * the well-known soundex algorithm.
+ *
+ *
+ * Algorithm
+ *
+ *
+ *
+ *
+ * Step 1:
+ * After preprocessing (conversion to upper case, transcription of germanic umlauts , removal of non alphabetical characters) the
+ * letters of the supplied text are replaced by their phonetic code according to the following table.
+ *
+ * (Source: Wikipedia (de): Kölner Phonetik --
+ * Buchstabencodes )
+ *
+ * Letter
+ * Context
+ * Code
+ *
+ *
+ * A, E, I, J, O, U, Y
+ *
+ * 0
+ *
+ *
+ *
+ * H
+ *
+ * -
+ *
+ *
+ * B
+ *
+ * 1
+ *
+ *
+ * P
+ * not before H
+ *
+ *
+ *
+ * D, T
+ * not before C, S, Z
+ * 2
+ *
+ *
+ * F, V, W
+ *
+ * 3
+ *
+ *
+ *
+ * P
+ * before H
+ *
+ *
+ * G, K, Q
+ *
+ * 4
+ *
+ *
+ * C
+ * at onset before A, H, K, L, O, Q, R, U, X
+ *
+ *
+ *
+ * before A, H, K, O, Q, U, X except after S, Z
+ *
+ *
+ * X
+ * not after C, K, Q
+ * 48
+ *
+ *
+ * L
+ *
+ *
+ * 5
+ *
+ *
+ * M, N
+ *
+ * 6
+ *
+ *
+ * R
+ *
+ * 7
+ *
+ *
+ *
+ * S, Z
+ *
+ * 8
+ *
+ *
+ * C
+ * after S, Z
+ *
+ *
+ * at onset except before A, H, K, L, O, Q, R, U, X
+ *
+ *
+ *
+ * not before A, H, K, O, Q, U, X
+ *
+ *
+ * D, T
+ * before C, S, Z
+ *
+ *
+ * X
+ * after C, K, Q
+ *
+ *
+ *
+ *
+ * Example:
+ *
+ * "M
üller-L
ü
+ * denscheidt" => "MULLERLUDENSCHEIDT" => "6005507500206880022"
+ *
+ *
+ *
+ *
+ * Step 2:
+ * Collapse of all multiple consecutive code digits.
+ * Example:
+ * "6005507500206880022" => "6050750206802"
+ *
+ *
+ * Step 3:
+ * Removal of all codes "0" except at the beginning. This means that two or more identical consecutive digits can occur
+ * if they occur after removing the "0" digits.
+ *
+ * Example:
+ * "6050750206802" => "65752682"
+ *
+ *
+ *
+ *
+ * This class is thread-safe.
+ *
+ *
+ * @see Wikipedia (de): Kölner Phonetik (in German)
+ * @since 1.5
+ */
+public class ColognePhonetic implements StringEncoder {
+
+ // Predefined char arrays for better performance and less GC load
+ private static final char[] AEIJOUY = new char[] { 'A', 'E', 'I', 'J', 'O', 'U', 'Y' };
+ private static final char[] SCZ = new char[] { 'S', 'C', 'Z' };
+ private static final char[] WFPV = new char[] { 'W', 'F', 'P', 'V' };
+ private static final char[] GKQ = new char[] { 'G', 'K', 'Q' };
+ private static final char[] CKQ = new char[] { 'C', 'K', 'Q' };
+ private static final char[] AHKLOQRUX = new char[] { 'A', 'H', 'K', 'L', 'O', 'Q', 'R', 'U', 'X' };
+ private static final char[] SZ = new char[] { 'S', 'Z' };
+ private static final char[] AHOUKQX = new char[] { 'A', 'H', 'O', 'U', 'K', 'Q', 'X' };
+ private static final char[] TDX = new char[] { 'T', 'D', 'X' };
+
+ /**
+ * This class is not thread-safe; the field {@link #length} is mutable.
+ * However, it is not shared between threads, as it is constructed on demand
+ * by the method {@link ColognePhonetic#colognePhonetic(String)}
+ */
+ private abstract class CologneBuffer {
+
+ protected final char[] data;
+
+ protected int length = 0;
+
+ public CologneBuffer(final char[] data) {
+ this.data = data;
+ this.length = data.length;
+ }
+
+ public CologneBuffer(final int buffSize) {
+ this.data = new char[buffSize];
+ this.length = 0;
+ }
+
+ protected abstract char[] copyData(int start, final int length);
+
+ public int length() {
+ return length;
+ }
+
+ @Override
+ public String toString() {
+ return new String(copyData(0, length));
+ }
+ }
+
+ private class CologneOutputBuffer extends CologneBuffer {
+
+ public CologneOutputBuffer(final int buffSize) {
+ super(buffSize);
+ }
+
+ public void addRight(final char chr) {
+ data[length] = chr;
+ length++;
+ }
+
+ @Override
+ protected char[] copyData(final int start, final int length) {
+ final char[] newData = new char[length];
+ System.arraycopy(data, start, newData, 0, length);
+ return newData;
+ }
+ }
+
+ private class CologneInputBuffer extends CologneBuffer {
+
+ public CologneInputBuffer(final char[] data) {
+ super(data);
+ }
+
+ public void addLeft(final char ch) {
+ length++;
+ data[getNextPos()] = ch;
+ }
+
+ @Override
+ protected char[] copyData(final int start, final int length) {
+ final char[] newData = new char[length];
+ System.arraycopy(data, data.length - this.length + start, newData, 0, length);
+ return newData;
+ }
+
+ public char getNextChar() {
+ return data[getNextPos()];
+ }
+
+ protected int getNextPos() {
+ return data.length - length;
+ }
+
+ public char removeNext() {
+ final char ch = getNextChar();
+ length--;
+ return ch;
+ }
+ }
+
+ /**
+ * Maps some Germanic characters to plain for internal processing. The following characters are mapped:
+ *
+ * capital a, umlaut mark
+ * capital u, umlaut mark
+ * capital o, umlaut mark
+ * small sharp s, German
+ *
+ */
+ private static final char[][] PREPROCESS_MAP = new char[][]{
+ {'\u00C4', 'A'}, // capital a, umlaut mark
+ {'\u00DC', 'U'}, // capital u, umlaut mark
+ {'\u00D6', 'O'}, // capital o, umlaut mark
+ {'\u00DF', 'S'} // small sharp s, German
+ };
+
+ /*
+ * Returns whether the array contains the key, or not.
+ */
+ private static boolean arrayContains(final char[] arr, final char key) {
+ for (final char element : arr) {
+ if (element == key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ *
+ * Implements the Kölner Phonetik algorithm.
+ *
+ *
+ * In contrast to the initial description of the algorithm, this implementation does the encoding in one pass.
+ *
+ *
+ * @param text The source text to encode
+ * @return the corresponding encoding according to the Kölner Phonetik algorithm
+ */
+ public String colognePhonetic(String text) {
+ if (text == null) {
+ return null;
+ }
+
+ text = preprocess(text);
+
+ final CologneOutputBuffer output = new CologneOutputBuffer(text.length() * 2);
+ final CologneInputBuffer input = new CologneInputBuffer(text.toCharArray());
+
+ char nextChar;
+
+ char lastChar = '-';
+ char lastCode = '/';
+ char code;
+ char chr;
+
+ int rightLength = input.length();
+
+ while (rightLength > 0) {
+ chr = input.removeNext();
+
+ if ((rightLength = input.length()) > 0) {
+ nextChar = input.getNextChar();
+ } else {
+ nextChar = '-';
+ }
+
+ if (arrayContains(AEIJOUY, chr)) {
+ code = '0';
+ } else if (chr == 'H' || chr < 'A' || chr > 'Z') {
+ if (lastCode == '/') {
+ continue;
+ }
+ code = '-';
+ } else if (chr == 'B' || (chr == 'P' && nextChar != 'H')) {
+ code = '1';
+ } else if ((chr == 'D' || chr == 'T') && !arrayContains(SCZ, nextChar)) {
+ code = '2';
+ } else if (arrayContains(WFPV, chr)) {
+ code = '3';
+ } else if (arrayContains(GKQ, chr)) {
+ code = '4';
+ } else if (chr == 'X' && !arrayContains(CKQ, lastChar)) {
+ code = '4';
+ input.addLeft('S');
+ rightLength++;
+ } else if (chr == 'S' || chr == 'Z') {
+ code = '8';
+ } else if (chr == 'C') {
+ if (lastCode == '/') {
+ if (arrayContains(AHKLOQRUX, nextChar)) {
+ code = '4';
+ } else {
+ code = '8';
+ }
+ } else {
+ if (arrayContains(SZ, lastChar) || !arrayContains(AHOUKQX, nextChar)) {
+ code = '8';
+ } else {
+ code = '4';
+ }
+ }
+ } else if (arrayContains(TDX, chr)) {
+ code = '8';
+ } else if (chr == 'R') {
+ code = '7';
+ } else if (chr == 'L') {
+ code = '5';
+ } else if (chr == 'M' || chr == 'N') {
+ code = '6';
+ } else {
+ code = chr;
+ }
+
+ if (code != '-' && (lastCode != code && (code != '0' || lastCode == '/') || code < '0' || code > '8')) {
+ output.addRight(code);
+ }
+
+ lastChar = chr;
+ lastCode = code;
+ }
+ return output.toString();
+ }
+
+ @Override
+ public Object encode(final Object object) throws EncoderException {
+ if (!(object instanceof String)) {
+ throw new EncoderException("This method's parameter was expected to be of the type " +
+ String.class.getName() +
+ ". But actually it was of the type " +
+ object.getClass().getName() +
+ ".");
+ }
+ return encode((String) object);
+ }
+
+ @Override
+ public String encode(final String text) {
+ return colognePhonetic(text);
+ }
+
+ public boolean isEncodeEqual(final String text1, final String text2) {
+ return colognePhonetic(text1).equals(colognePhonetic(text2));
+ }
+
+ /**
+ * Converts the string to upper case and replaces germanic characters as defined in {@link #PREPROCESS_MAP}.
+ */
+ private String preprocess(String text) {
+ text = text.toUpperCase(Locale.GERMAN);
+
+ final char[] chrs = text.toCharArray();
+
+ for (int index = 0; index < chrs.length; index++) {
+ if (chrs[index] > 'Z') {
+ for (final char[] element : PREPROCESS_MAP) {
+ if (chrs[index] == element[0]) {
+ chrs[index] = element[1];
+ break;
+ }
+ }
+ }
+ }
+ return new String(chrs);
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/DaitchMokotoffSoundex.java b/src/main/java/org/apache/commons/codec/language/DaitchMokotoffSoundex.java
new file mode 100644
index 00000000..b33dbe83
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/DaitchMokotoffSoundex.java
@@ -0,0 +1,561 @@
+/*
+ * 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.commons.codec.language;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.commons.codec.CharEncoding;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Daitch-Mokotoff Soundex value.
+ *
+ * The Daitch-Mokotoff Soundex algorithm is a refinement of the Russel and American Soundex algorithms, yielding greater
+ * accuracy in matching especially Slavish and Yiddish surnames with similar pronunciation but differences in spelling.
+ *
+ *
+ * The main differences compared to the other soundex variants are:
+ *
+ *
+ * coded names are 6 digits long
+ * the initial character of the name is coded
+ * rules to encoded multi-character n-grams
+ * multiple possible encodings for the same name (branching)
+ *
+ *
+ * This implementation supports branching, depending on the used method:
+ *
+ * {@link #encode(String)} - branching disabled, only the first code will be returned
+ * {@link #soundex(String)} - branching enabled, all codes will be returned, separated by '|'
+ *
+ *
+ * Note: this implementation has additional branching rules compared to the original description of the algorithm. The
+ * rules can be customized by overriding the default rules contained in the resource file
+ * {@code org/apache/commons/codec/language/dmrules.txt}.
+ *
+ *
+ * This class is thread-safe.
+ *
+ *
+ * @see Soundex
+ * @see Wikipedia - Daitch-Mokotoff Soundex
+ * @see Avotaynu - Soundexing and Genealogy
+ *
+ * @version $Id$
+ * @since 1.10
+ */
+public class DaitchMokotoffSoundex implements StringEncoder {
+
+ /**
+ * Inner class representing a branch during DM soundex encoding.
+ */
+ private static final class Branch {
+ private final StringBuilder builder;
+ private String cachedString;
+ private String lastReplacement;
+
+ private Branch() {
+ builder = new StringBuilder();
+ lastReplacement = null;
+ cachedString = null;
+ }
+
+ /**
+ * Creates a new branch, identical to this branch.
+ *
+ * @return a new, identical branch
+ */
+ public Branch createBranch() {
+ final Branch branch = new Branch();
+ branch.builder.append(toString());
+ branch.lastReplacement = this.lastReplacement;
+ return branch;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Branch)) {
+ return false;
+ }
+
+ return toString().equals(((Branch) other).toString());
+ }
+
+ /**
+ * Finish this branch by appending '0's until the maximum code length has been reached.
+ */
+ public void finish() {
+ while (builder.length() < MAX_LENGTH) {
+ builder.append('0');
+ cachedString = null;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Process the next replacement to be added to this branch.
+ *
+ * @param replacement
+ * the next replacement to append
+ * @param forceAppend
+ * indicates if the default processing shall be overridden
+ */
+ public void processNextReplacement(final String replacement, final boolean forceAppend) {
+ final boolean append = lastReplacement == null || !lastReplacement.endsWith(replacement) || forceAppend;
+
+ if (append && builder.length() < MAX_LENGTH) {
+ builder.append(replacement);
+ // remove all characters after the maximum length
+ if (builder.length() > MAX_LENGTH) {
+ builder.delete(MAX_LENGTH, builder.length());
+ }
+ cachedString = null;
+ }
+
+ lastReplacement = replacement;
+ }
+
+ @Override
+ public String toString() {
+ if (cachedString == null) {
+ cachedString = builder.toString();
+ }
+ return cachedString;
+ }
+ }
+
+ /**
+ * Inner class for storing rules.
+ */
+ private static final class Rule {
+ private final String pattern;
+ private final String[] replacementAtStart;
+ private final String[] replacementBeforeVowel;
+ private final String[] replacementDefault;
+
+ protected Rule(final String pattern, final String replacementAtStart, final String replacementBeforeVowel,
+ final String replacementDefault) {
+ this.pattern = pattern;
+ this.replacementAtStart = replacementAtStart.split("\\|");
+ this.replacementBeforeVowel = replacementBeforeVowel.split("\\|");
+ this.replacementDefault = replacementDefault.split("\\|");
+ }
+
+ public int getPatternLength() {
+ return pattern.length();
+ }
+
+ public String[] getReplacements(final String context, final boolean atStart) {
+ if (atStart) {
+ return replacementAtStart;
+ }
+
+ final int nextIndex = getPatternLength();
+ final boolean nextCharIsVowel = nextIndex < context.length() ? isVowel(context.charAt(nextIndex)) : false;
+ if (nextCharIsVowel) {
+ return replacementBeforeVowel;
+ }
+
+ return replacementDefault;
+ }
+
+ private boolean isVowel(final char ch) {
+ return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
+ }
+
+ public boolean matches(final String context) {
+ return context.startsWith(pattern);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s=(%s,%s,%s)", pattern, Arrays.asList(replacementAtStart),
+ Arrays.asList(replacementBeforeVowel), Arrays.asList(replacementDefault));
+ }
+ }
+
+ private static final String COMMENT = "//";
+ private static final String DOUBLE_QUOTE = "\"";
+
+ private static final String MULTILINE_COMMENT_END = "*/";
+
+ private static final String MULTILINE_COMMENT_START = "/*";
+
+ /** The resource file containing the replacement and folding rules */
+ private static final String RESOURCE_FILE = "org/apache/commons/codec/language/dmrules.txt";
+
+ /** The code length of a DM soundex value. */
+ private static final int MAX_LENGTH = 6;
+
+ /** Transformation rules indexed by the first character of their pattern. */
+ private static final Map> RULES = new HashMap>();
+
+ /** Folding rules. */
+ private static final Map FOLDINGS = new HashMap();
+
+ static {
+ final InputStream rulesIS = DaitchMokotoffSoundex.class.getClassLoader().getResourceAsStream(RESOURCE_FILE);
+ if (rulesIS == null) {
+ throw new IllegalArgumentException("Unable to load resource: " + RESOURCE_FILE);
+ }
+
+ final Scanner scanner = new Scanner(rulesIS, CharEncoding.UTF_8);
+ parseRules(scanner, RESOURCE_FILE, RULES, FOLDINGS);
+ scanner.close();
+
+ // sort RULES by pattern length in descending order
+ for (final Map.Entry> rule : RULES.entrySet()) {
+ final List ruleList = rule.getValue();
+ Collections.sort(ruleList, new Comparator() {
+ @Override
+ public int compare(final Rule rule1, final Rule rule2) {
+ return rule2.getPatternLength() - rule1.getPatternLength();
+ }
+ });
+ }
+ }
+
+ private static void parseRules(final Scanner scanner, final String location,
+ final Map> ruleMapping, final Map asciiFoldings) {
+ int currentLine = 0;
+ boolean inMultilineComment = false;
+
+ while (scanner.hasNextLine()) {
+ currentLine++;
+ final String rawLine = scanner.nextLine();
+ String line = rawLine;
+
+ if (inMultilineComment) {
+ if (line.endsWith(MULTILINE_COMMENT_END)) {
+ inMultilineComment = false;
+ }
+ continue;
+ }
+
+ if (line.startsWith(MULTILINE_COMMENT_START)) {
+ inMultilineComment = true;
+ } else {
+ // discard comments
+ final int cmtI = line.indexOf(COMMENT);
+ if (cmtI >= 0) {
+ line = line.substring(0, cmtI);
+ }
+
+ // trim leading-trailing whitespace
+ line = line.trim();
+
+ if (line.length() == 0) {
+ continue; // empty lines can be safely skipped
+ }
+
+ if (line.contains("=")) {
+ // folding
+ final String[] parts = line.split("=");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException("Malformed folding statement split into " + parts.length +
+ " parts: " + rawLine + " in " + location);
+ } else {
+ final String leftCharacter = parts[0];
+ final String rightCharacter = parts[1];
+
+ if (leftCharacter.length() != 1 || rightCharacter.length() != 1) {
+ throw new IllegalArgumentException("Malformed folding statement - " +
+ "patterns are not single characters: " + rawLine + " in " + location);
+ }
+
+ asciiFoldings.put(leftCharacter.charAt(0), rightCharacter.charAt(0));
+ }
+ } else {
+ // rule
+ final String[] parts = line.split("\\s+");
+ if (parts.length != 4) {
+ throw new IllegalArgumentException("Malformed rule statement split into " + parts.length +
+ " parts: " + rawLine + " in " + location);
+ } else {
+ try {
+ final String pattern = stripQuotes(parts[0]);
+ final String replacement1 = stripQuotes(parts[1]);
+ final String replacement2 = stripQuotes(parts[2]);
+ final String replacement3 = stripQuotes(parts[3]);
+
+ final Rule r = new Rule(pattern, replacement1, replacement2, replacement3);
+ final char patternKey = r.pattern.charAt(0);
+ List rules = ruleMapping.get(patternKey);
+ if (rules == null) {
+ rules = new ArrayList();
+ ruleMapping.put(patternKey, rules);
+ }
+ rules.add(r);
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalStateException(
+ "Problem parsing line '" + currentLine + "' in " + location, e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static String stripQuotes(String str) {
+ if (str.startsWith(DOUBLE_QUOTE)) {
+ str = str.substring(1);
+ }
+
+ if (str.endsWith(DOUBLE_QUOTE)) {
+ str = str.substring(0, str.length() - 1);
+ }
+
+ return str;
+ }
+
+ /** Whether to use ASCII folding prior to encoding. */
+ private final boolean folding;
+
+ /**
+ * Creates a new instance with ASCII-folding enabled.
+ */
+ public DaitchMokotoffSoundex() {
+ this(true);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * With ASCII-folding enabled, certain accented characters will be transformed to equivalent ASCII characters, e.g.
+ * è -> e.
+ *
+ *
+ * @param folding
+ * if ASCII-folding shall be performed before encoding
+ */
+ public DaitchMokotoffSoundex(final boolean folding) {
+ this.folding = folding;
+ }
+
+ /**
+ * Performs a cleanup of the input string before the actual soundex transformation.
+ *
+ * Removes all whitespace characters and performs ASCII folding if enabled.
+ *
+ *
+ * @param input
+ * the input string to cleanup
+ * @return a cleaned up string
+ */
+ private String cleanup(final String input) {
+ final StringBuilder sb = new StringBuilder();
+ for (char ch : input.toCharArray()) {
+ if (Character.isWhitespace(ch)) {
+ continue;
+ }
+
+ ch = Character.toLowerCase(ch);
+ if (folding && FOLDINGS.containsKey(ch)) {
+ ch = FOLDINGS.get(ch);
+ }
+ sb.append(ch);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Encodes an Object using the Daitch-Mokotoff soundex algorithm without branching.
+ *
+ * This method is provided in order to satisfy the requirements of the Encoder interface, and will throw an
+ * EncoderException if the supplied object is not of type java.lang.String.
+ *
+ *
+ * @see #soundex(String)
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (of type java.lang.String) containing the DM soundex code, which corresponds to the String
+ * supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException(
+ "Parameter supplied to DaitchMokotoffSoundex encode is not of type java.lang.String");
+ }
+ return encode((String) obj);
+ }
+
+ /**
+ * Encodes a String using the Daitch-Mokotoff soundex algorithm without branching.
+ *
+ * @see #soundex(String)
+ *
+ * @param source
+ * A String object to encode
+ * @return A DM Soundex code corresponding to the String supplied
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public String encode(final String source) {
+ if (source == null) {
+ return null;
+ }
+ return soundex(source, false)[0];
+ }
+
+ /**
+ * Encodes a String using the Daitch-Mokotoff soundex algorithm with branching.
+ *
+ * In case a string is encoded into multiple codes (see branching rules), the result will contain all codes,
+ * separated by '|'.
+ *
+ *
+ * Example: the name "AUERBACH" is encoded as both
+ *
+ *
+ * 097400
+ * 097500
+ *
+ *
+ * Thus the result will be "097400|097500".
+ *
+ *
+ * @param source
+ * A String object to encode
+ * @return A string containing a set of DM Soundex codes corresponding to the String supplied
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ public String soundex(final String source) {
+ final String[] branches = soundex(source, true);
+ final StringBuilder sb = new StringBuilder();
+ int index = 0;
+ for (final String branch : branches) {
+ sb.append(branch);
+ if (++index < branches.length) {
+ sb.append('|');
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Perform the actual DM Soundex algorithm on the input string.
+ *
+ * @param source
+ * A String object to encode
+ * @param branching
+ * If branching shall be performed
+ * @return A string array containing all DM Soundex codes corresponding to the String supplied depending on the
+ * selected branching mode
+ */
+ private String[] soundex(final String source, final boolean branching) {
+ if (source == null) {
+ return null;
+ }
+
+ final String input = cleanup(source);
+
+ final Set currentBranches = new LinkedHashSet();
+ currentBranches.add(new Branch());
+
+ char lastChar = '\0';
+ for (int index = 0; index < input.length(); index++) {
+ final char ch = input.charAt(index);
+
+ // ignore whitespace inside a name
+ if (Character.isWhitespace(ch)) {
+ continue;
+ }
+
+ final String inputContext = input.substring(index);
+ final List rules = RULES.get(ch);
+ if (rules == null) {
+ continue;
+ }
+
+ // use an EMPTY_LIST to avoid false positive warnings wrt potential null pointer access
+ @SuppressWarnings("unchecked")
+ final List nextBranches = branching ? new ArrayList() : Collections.EMPTY_LIST;
+
+ for (final Rule rule : rules) {
+ if (rule.matches(inputContext)) {
+ if (branching) {
+ nextBranches.clear();
+ }
+ final String[] replacements = rule.getReplacements(inputContext, lastChar == '\0');
+ final boolean branchingRequired = replacements.length > 1 && branching;
+
+ for (final Branch branch : currentBranches) {
+ for (final String nextReplacement : replacements) {
+ // if we have multiple replacements, always create a new branch
+ final Branch nextBranch = branchingRequired ? branch.createBranch() : branch;
+
+ // special rule: occurrences of mn or nm are treated differently
+ final boolean force = (lastChar == 'm' && ch == 'n') || (lastChar == 'n' && ch == 'm');
+
+ nextBranch.processNextReplacement(nextReplacement, force);
+
+ if (branching) {
+ nextBranches.add(nextBranch);
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (branching) {
+ currentBranches.clear();
+ currentBranches.addAll(nextBranches);
+ }
+ index += rule.getPatternLength() - 1;
+ break;
+ }
+ }
+
+ lastChar = ch;
+ }
+
+ final String[] result = new String[currentBranches.size()];
+ int index = 0;
+ for (final Branch branch : currentBranches) {
+ branch.finish();
+ result[index++] = branch.toString();
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/DoubleMetaphone.java b/src/main/java/org/apache/commons/codec/language/DoubleMetaphone.java
new file mode 100644
index 00000000..211e57fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/DoubleMetaphone.java
@@ -0,0 +1,1009 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Encodes a string into a double metaphone value. This Implementation is based on the algorithm by Lawrence
+ * Philips .
+ *
+ * This class is conditionally thread-safe. The instance field {@link #maxCodeLen} is mutable
+ * {@link #setMaxCodeLen(int)} but is not volatile, and accesses are not synchronized. If an instance of the class is
+ * shared between threads, the caller needs to ensure that suitable synchronization is used to ensure safe publication
+ * of the value between threads, and must not invoke {@link #setMaxCodeLen(int)} after initial setup.
+ *
+ * @see Original Article
+ * @see http://en.wikipedia.org/wiki/Metaphone
+ *
+ * @version $Id$
+ */
+public class DoubleMetaphone implements StringEncoder {
+
+ /**
+ * "Vowels" to test for
+ */
+ private static final String VOWELS = "AEIOUY";
+
+ /**
+ * Prefixes when present which are not pronounced
+ */
+ private static final String[] SILENT_START =
+ { "GN", "KN", "PN", "WR", "PS" };
+ private static final String[] L_R_N_M_B_H_F_V_W_SPACE =
+ { "L", "R", "N", "M", "B", "H", "F", "V", "W", " " };
+ private static final String[] ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER =
+ { "ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER" };
+ private static final String[] L_T_K_S_N_M_B_Z =
+ { "L", "T", "K", "S", "N", "M", "B", "Z" };
+
+ /**
+ * Maximum length of an encoding, default is 4
+ */
+ private int maxCodeLen = 4;
+
+ /**
+ * Creates an instance of this DoubleMetaphone encoder
+ */
+ public DoubleMetaphone() {
+ super();
+ }
+
+ /**
+ * Encode a value with Double Metaphone.
+ *
+ * @param value String to encode
+ * @return an encoded string
+ */
+ public String doubleMetaphone(final String value) {
+ return doubleMetaphone(value, false);
+ }
+
+ /**
+ * Encode a value with Double Metaphone, optionally using the alternate encoding.
+ *
+ * @param value String to encode
+ * @param alternate use alternate encode
+ * @return an encoded string
+ */
+ public String doubleMetaphone(String value, final boolean alternate) {
+ value = cleanInput(value);
+ if (value == null) {
+ return null;
+ }
+
+ final boolean slavoGermanic = isSlavoGermanic(value);
+ int index = isSilentStart(value) ? 1 : 0;
+
+ final DoubleMetaphoneResult result = new DoubleMetaphoneResult(this.getMaxCodeLen());
+
+ while (!result.isComplete() && index <= value.length() - 1) {
+ switch (value.charAt(index)) {
+ case 'A':
+ case 'E':
+ case 'I':
+ case 'O':
+ case 'U':
+ case 'Y':
+ index = handleAEIOUY(result, index);
+ break;
+ case 'B':
+ result.append('P');
+ index = charAt(value, index + 1) == 'B' ? index + 2 : index + 1;
+ break;
+ case '\u00C7':
+ // A C with a Cedilla
+ result.append('S');
+ index++;
+ break;
+ case 'C':
+ index = handleC(value, result, index);
+ break;
+ case 'D':
+ index = handleD(value, result, index);
+ break;
+ case 'F':
+ result.append('F');
+ index = charAt(value, index + 1) == 'F' ? index + 2 : index + 1;
+ break;
+ case 'G':
+ index = handleG(value, result, index, slavoGermanic);
+ break;
+ case 'H':
+ index = handleH(value, result, index);
+ break;
+ case 'J':
+ index = handleJ(value, result, index, slavoGermanic);
+ break;
+ case 'K':
+ result.append('K');
+ index = charAt(value, index + 1) == 'K' ? index + 2 : index + 1;
+ break;
+ case 'L':
+ index = handleL(value, result, index);
+ break;
+ case 'M':
+ result.append('M');
+ index = conditionM0(value, index) ? index + 2 : index + 1;
+ break;
+ case 'N':
+ result.append('N');
+ index = charAt(value, index + 1) == 'N' ? index + 2 : index + 1;
+ break;
+ case '\u00D1':
+ // N with a tilde (spanish ene)
+ result.append('N');
+ index++;
+ break;
+ case 'P':
+ index = handleP(value, result, index);
+ break;
+ case 'Q':
+ result.append('K');
+ index = charAt(value, index + 1) == 'Q' ? index + 2 : index + 1;
+ break;
+ case 'R':
+ index = handleR(value, result, index, slavoGermanic);
+ break;
+ case 'S':
+ index = handleS(value, result, index, slavoGermanic);
+ break;
+ case 'T':
+ index = handleT(value, result, index);
+ break;
+ case 'V':
+ result.append('F');
+ index = charAt(value, index + 1) == 'V' ? index + 2 : index + 1;
+ break;
+ case 'W':
+ index = handleW(value, result, index);
+ break;
+ case 'X':
+ index = handleX(value, result, index);
+ break;
+ case 'Z':
+ index = handleZ(value, result, index, slavoGermanic);
+ break;
+ default:
+ index++;
+ break;
+ }
+ }
+
+ return alternate ? result.getAlternate() : result.getPrimary();
+ }
+
+ /**
+ * Encode the value using DoubleMetaphone. It will only work if
+ * obj
is a String
(like Metaphone
).
+ *
+ * @param obj Object to encode (should be of type String)
+ * @return An encoded Object (will be of type String)
+ * @throws EncoderException encode parameter is not of type String
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("DoubleMetaphone encode parameter is not of type String");
+ }
+ return doubleMetaphone((String) obj);
+ }
+
+ /**
+ * Encode the value using DoubleMetaphone.
+ *
+ * @param value String to encode
+ * @return An encoded String
+ */
+ @Override
+ public String encode(final String value) {
+ return doubleMetaphone(value);
+ }
+
+ /**
+ * Check if the Double Metaphone values of two String
values
+ * are equal.
+ *
+ * @param value1 The left-hand side of the encoded {@link String#equals(Object)}.
+ * @param value2 The right-hand side of the encoded {@link String#equals(Object)}.
+ * @return true
if the encoded String
s are equal;
+ * false
otherwise.
+ * @see #isDoubleMetaphoneEqual(String,String,boolean)
+ */
+ public boolean isDoubleMetaphoneEqual(final String value1, final String value2) {
+ return isDoubleMetaphoneEqual(value1, value2, false);
+ }
+
+ /**
+ * Check if the Double Metaphone values of two String
values
+ * are equal, optionally using the alternate value.
+ *
+ * @param value1 The left-hand side of the encoded {@link String#equals(Object)}.
+ * @param value2 The right-hand side of the encoded {@link String#equals(Object)}.
+ * @param alternate use the alternate value if true
.
+ * @return true
if the encoded String
s are equal;
+ * false
otherwise.
+ */
+ public boolean isDoubleMetaphoneEqual(final String value1, final String value2, final boolean alternate) {
+ return StringUtils.equals(doubleMetaphone(value1, alternate), doubleMetaphone(value2, alternate));
+ }
+
+ /**
+ * Returns the maxCodeLen.
+ * @return int
+ */
+ public int getMaxCodeLen() {
+ return this.maxCodeLen;
+ }
+
+ /**
+ * Sets the maxCodeLen.
+ * @param maxCodeLen The maxCodeLen to set
+ */
+ public void setMaxCodeLen(final int maxCodeLen) {
+ this.maxCodeLen = maxCodeLen;
+ }
+
+ //-- BEGIN HANDLERS --//
+
+ /**
+ * Handles 'A', 'E', 'I', 'O', 'U', and 'Y' cases.
+ */
+ private int handleAEIOUY(final DoubleMetaphoneResult result, final int index) {
+ if (index == 0) {
+ result.append('A');
+ }
+ return index + 1;
+ }
+
+ /**
+ * Handles 'C' cases.
+ */
+ private int handleC(final String value, final DoubleMetaphoneResult result, int index) {
+ if (conditionC0(value, index)) { // very confusing, moved out
+ result.append('K');
+ index += 2;
+ } else if (index == 0 && contains(value, index, 6, "CAESAR")) {
+ result.append('S');
+ index += 2;
+ } else if (contains(value, index, 2, "CH")) {
+ index = handleCH(value, result, index);
+ } else if (contains(value, index, 2, "CZ") &&
+ !contains(value, index - 2, 4, "WICZ")) {
+ //-- "Czerny" --//
+ result.append('S', 'X');
+ index += 2;
+ } else if (contains(value, index + 1, 3, "CIA")) {
+ //-- "focaccia" --//
+ result.append('X');
+ index += 3;
+ } else if (contains(value, index, 2, "CC") &&
+ !(index == 1 && charAt(value, 0) == 'M')) {
+ //-- double "cc" but not "McClelland" --//
+ return handleCC(value, result, index);
+ } else if (contains(value, index, 2, "CK", "CG", "CQ")) {
+ result.append('K');
+ index += 2;
+ } else if (contains(value, index, 2, "CI", "CE", "CY")) {
+ //-- Italian vs. English --//
+ if (contains(value, index, 3, "CIO", "CIE", "CIA")) {
+ result.append('S', 'X');
+ } else {
+ result.append('S');
+ }
+ index += 2;
+ } else {
+ result.append('K');
+ if (contains(value, index + 1, 2, " C", " Q", " G")) {
+ //-- Mac Caffrey, Mac Gregor --//
+ index += 3;
+ } else if (contains(value, index + 1, 1, "C", "K", "Q") &&
+ !contains(value, index + 1, 2, "CE", "CI")) {
+ index += 2;
+ } else {
+ index++;
+ }
+ }
+
+ return index;
+ }
+
+ /**
+ * Handles 'CC' cases.
+ */
+ private int handleCC(final String value, final DoubleMetaphoneResult result, int index) {
+ if (contains(value, index + 2, 1, "I", "E", "H") &&
+ !contains(value, index + 2, 2, "HU")) {
+ //-- "bellocchio" but not "bacchus" --//
+ if ((index == 1 && charAt(value, index - 1) == 'A') ||
+ contains(value, index - 1, 5, "UCCEE", "UCCES")) {
+ //-- "accident", "accede", "succeed" --//
+ result.append("KS");
+ } else {
+ //-- "bacci", "bertucci", other Italian --//
+ result.append('X');
+ }
+ index += 3;
+ } else { // Pierce's rule
+ result.append('K');
+ index += 2;
+ }
+
+ return index;
+ }
+
+ /**
+ * Handles 'CH' cases.
+ */
+ private int handleCH(final String value, final DoubleMetaphoneResult result, final int index) {
+ if (index > 0 && contains(value, index, 4, "CHAE")) { // Michael
+ result.append('K', 'X');
+ return index + 2;
+ } else if (conditionCH0(value, index)) {
+ //-- Greek roots ("chemistry", "chorus", etc.) --//
+ result.append('K');
+ return index + 2;
+ } else if (conditionCH1(value, index)) {
+ //-- Germanic, Greek, or otherwise 'ch' for 'kh' sound --//
+ result.append('K');
+ return index + 2;
+ } else {
+ if (index > 0) {
+ if (contains(value, 0, 2, "MC")) {
+ result.append('K');
+ } else {
+ result.append('X', 'K');
+ }
+ } else {
+ result.append('X');
+ }
+ return index + 2;
+ }
+ }
+
+ /**
+ * Handles 'D' cases.
+ */
+ private int handleD(final String value, final DoubleMetaphoneResult result, int index) {
+ if (contains(value, index, 2, "DG")) {
+ //-- "Edge" --//
+ if (contains(value, index + 2, 1, "I", "E", "Y")) {
+ result.append('J');
+ index += 3;
+ //-- "Edgar" --//
+ } else {
+ result.append("TK");
+ index += 2;
+ }
+ } else if (contains(value, index, 2, "DT", "DD")) {
+ result.append('T');
+ index += 2;
+ } else {
+ result.append('T');
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'G' cases.
+ */
+ private int handleG(final String value, final DoubleMetaphoneResult result, int index,
+ final boolean slavoGermanic) {
+ if (charAt(value, index + 1) == 'H') {
+ index = handleGH(value, result, index);
+ } else if (charAt(value, index + 1) == 'N') {
+ if (index == 1 && isVowel(charAt(value, 0)) && !slavoGermanic) {
+ result.append("KN", "N");
+ } else if (!contains(value, index + 2, 2, "EY") &&
+ charAt(value, index + 1) != 'Y' && !slavoGermanic) {
+ result.append("N", "KN");
+ } else {
+ result.append("KN");
+ }
+ index = index + 2;
+ } else if (contains(value, index + 1, 2, "LI") && !slavoGermanic) {
+ result.append("KL", "L");
+ index += 2;
+ } else if (index == 0 &&
+ (charAt(value, index + 1) == 'Y' ||
+ contains(value, index + 1, 2, ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER))) {
+ //-- -ges-, -gep-, -gel-, -gie- at beginning --//
+ result.append('K', 'J');
+ index += 2;
+ } else if ((contains(value, index + 1, 2, "ER") ||
+ charAt(value, index + 1) == 'Y') &&
+ !contains(value, 0, 6, "DANGER", "RANGER", "MANGER") &&
+ !contains(value, index - 1, 1, "E", "I") &&
+ !contains(value, index - 1, 3, "RGY", "OGY")) {
+ //-- -ger-, -gy- --//
+ result.append('K', 'J');
+ index += 2;
+ } else if (contains(value, index + 1, 1, "E", "I", "Y") ||
+ contains(value, index - 1, 4, "AGGI", "OGGI")) {
+ //-- Italian "biaggi" --//
+ if (contains(value, 0 ,4, "VAN ", "VON ") ||
+ contains(value, 0, 3, "SCH") ||
+ contains(value, index + 1, 2, "ET")) {
+ //-- obvious germanic --//
+ result.append('K');
+ } else if (contains(value, index + 1, 3, "IER")) {
+ result.append('J');
+ } else {
+ result.append('J', 'K');
+ }
+ index += 2;
+ } else if (charAt(value, index + 1) == 'G') {
+ index += 2;
+ result.append('K');
+ } else {
+ index++;
+ result.append('K');
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'GH' cases.
+ */
+ private int handleGH(final String value, final DoubleMetaphoneResult result, int index) {
+ if (index > 0 && !isVowel(charAt(value, index - 1))) {
+ result.append('K');
+ index += 2;
+ } else if (index == 0) {
+ if (charAt(value, index + 2) == 'I') {
+ result.append('J');
+ } else {
+ result.append('K');
+ }
+ index += 2;
+ } else if ((index > 1 && contains(value, index - 2, 1, "B", "H", "D")) ||
+ (index > 2 && contains(value, index - 3, 1, "B", "H", "D")) ||
+ (index > 3 && contains(value, index - 4, 1, "B", "H"))) {
+ //-- Parker's rule (with some further refinements) - "hugh"
+ index += 2;
+ } else {
+ if (index > 2 && charAt(value, index - 1) == 'U' &&
+ contains(value, index - 3, 1, "C", "G", "L", "R", "T")) {
+ //-- "laugh", "McLaughlin", "cough", "gough", "rough", "tough"
+ result.append('F');
+ } else if (index > 0 && charAt(value, index - 1) != 'I') {
+ result.append('K');
+ }
+ index += 2;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'H' cases.
+ */
+ private int handleH(final String value, final DoubleMetaphoneResult result, int index) {
+ //-- only keep if first & before vowel or between 2 vowels --//
+ if ((index == 0 || isVowel(charAt(value, index - 1))) &&
+ isVowel(charAt(value, index + 1))) {
+ result.append('H');
+ index += 2;
+ //-- also takes car of "HH" --//
+ } else {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'J' cases.
+ */
+ private int handleJ(final String value, final DoubleMetaphoneResult result, int index,
+ final boolean slavoGermanic) {
+ if (contains(value, index, 4, "JOSE") || contains(value, 0, 4, "SAN ")) {
+ //-- obvious Spanish, "Jose", "San Jacinto" --//
+ if ((index == 0 && (charAt(value, index + 4) == ' ') ||
+ value.length() == 4) || contains(value, 0, 4, "SAN ")) {
+ result.append('H');
+ } else {
+ result.append('J', 'H');
+ }
+ index++;
+ } else {
+ if (index == 0 && !contains(value, index, 4, "JOSE")) {
+ result.append('J', 'A');
+ } else if (isVowel(charAt(value, index - 1)) && !slavoGermanic &&
+ (charAt(value, index + 1) == 'A' || charAt(value, index + 1) == 'O')) {
+ result.append('J', 'H');
+ } else if (index == value.length() - 1) {
+ result.append('J', ' ');
+ } else if (!contains(value, index + 1, 1, L_T_K_S_N_M_B_Z) &&
+ !contains(value, index - 1, 1, "S", "K", "L")) {
+ result.append('J');
+ }
+
+ if (charAt(value, index + 1) == 'J') {
+ index += 2;
+ } else {
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'L' cases.
+ */
+ private int handleL(final String value, final DoubleMetaphoneResult result, int index) {
+ if (charAt(value, index + 1) == 'L') {
+ if (conditionL0(value, index)) {
+ result.appendPrimary('L');
+ } else {
+ result.append('L');
+ }
+ index += 2;
+ } else {
+ index++;
+ result.append('L');
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'P' cases.
+ */
+ private int handleP(final String value, final DoubleMetaphoneResult result, int index) {
+ if (charAt(value, index + 1) == 'H') {
+ result.append('F');
+ index += 2;
+ } else {
+ result.append('P');
+ index = contains(value, index + 1, 1, "P", "B") ? index + 2 : index + 1;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'R' cases.
+ */
+ private int handleR(final String value, final DoubleMetaphoneResult result, final int index,
+ final boolean slavoGermanic) {
+ if (index == value.length() - 1 && !slavoGermanic &&
+ contains(value, index - 2, 2, "IE") &&
+ !contains(value, index - 4, 2, "ME", "MA")) {
+ result.appendAlternate('R');
+ } else {
+ result.append('R');
+ }
+ return charAt(value, index + 1) == 'R' ? index + 2 : index + 1;
+ }
+
+ /**
+ * Handles 'S' cases.
+ */
+ private int handleS(final String value, final DoubleMetaphoneResult result, int index,
+ final boolean slavoGermanic) {
+ if (contains(value, index - 1, 3, "ISL", "YSL")) {
+ //-- special cases "island", "isle", "carlisle", "carlysle" --//
+ index++;
+ } else if (index == 0 && contains(value, index, 5, "SUGAR")) {
+ //-- special case "sugar-" --//
+ result.append('X', 'S');
+ index++;
+ } else if (contains(value, index, 2, "SH")) {
+ if (contains(value, index + 1, 4, "HEIM", "HOEK", "HOLM", "HOLZ")) {
+ //-- germanic --//
+ result.append('S');
+ } else {
+ result.append('X');
+ }
+ index += 2;
+ } else if (contains(value, index, 3, "SIO", "SIA") || contains(value, index, 4, "SIAN")) {
+ //-- Italian and Armenian --//
+ if (slavoGermanic) {
+ result.append('S');
+ } else {
+ result.append('S', 'X');
+ }
+ index += 3;
+ } else if ((index == 0 && contains(value, index + 1, 1, "M", "N", "L", "W")) ||
+ contains(value, index + 1, 1, "Z")) {
+ //-- german & anglicisations, e.g. "smith" match "schmidt" //
+ // "snider" match "schneider" --//
+ //-- also, -sz- in slavic language although in hungarian it //
+ // is pronounced "s" --//
+ result.append('S', 'X');
+ index = contains(value, index + 1, 1, "Z") ? index + 2 : index + 1;
+ } else if (contains(value, index, 2, "SC")) {
+ index = handleSC(value, result, index);
+ } else {
+ if (index == value.length() - 1 && contains(value, index - 2, 2, "AI", "OI")) {
+ //-- french e.g. "resnais", "artois" --//
+ result.appendAlternate('S');
+ } else {
+ result.append('S');
+ }
+ index = contains(value, index + 1, 1, "S", "Z") ? index + 2 : index + 1;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'SC' cases.
+ */
+ private int handleSC(final String value, final DoubleMetaphoneResult result, final int index) {
+ if (charAt(value, index + 2) == 'H') {
+ //-- Schlesinger's rule --//
+ if (contains(value, index + 3, 2, "OO", "ER", "EN", "UY", "ED", "EM")) {
+ //-- Dutch origin, e.g. "school", "schooner" --//
+ if (contains(value, index + 3, 2, "ER", "EN")) {
+ //-- "schermerhorn", "schenker" --//
+ result.append("X", "SK");
+ } else {
+ result.append("SK");
+ }
+ } else {
+ if (index == 0 && !isVowel(charAt(value, 3)) && charAt(value, 3) != 'W') {
+ result.append('X', 'S');
+ } else {
+ result.append('X');
+ }
+ }
+ } else if (contains(value, index + 2, 1, "I", "E", "Y")) {
+ result.append('S');
+ } else {
+ result.append("SK");
+ }
+ return index + 3;
+ }
+
+ /**
+ * Handles 'T' cases.
+ */
+ private int handleT(final String value, final DoubleMetaphoneResult result, int index) {
+ if (contains(value, index, 4, "TION")) {
+ result.append('X');
+ index += 3;
+ } else if (contains(value, index, 3, "TIA", "TCH")) {
+ result.append('X');
+ index += 3;
+ } else if (contains(value, index, 2, "TH") || contains(value, index, 3, "TTH")) {
+ if (contains(value, index + 2, 2, "OM", "AM") ||
+ //-- special case "thomas", "thames" or germanic --//
+ contains(value, 0, 4, "VAN ", "VON ") ||
+ contains(value, 0, 3, "SCH")) {
+ result.append('T');
+ } else {
+ result.append('0', 'T');
+ }
+ index += 2;
+ } else {
+ result.append('T');
+ index = contains(value, index + 1, 1, "T", "D") ? index + 2 : index + 1;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'W' cases.
+ */
+ private int handleW(final String value, final DoubleMetaphoneResult result, int index) {
+ if (contains(value, index, 2, "WR")) {
+ //-- can also be in middle of word --//
+ result.append('R');
+ index += 2;
+ } else {
+ if (index == 0 && (isVowel(charAt(value, index + 1)) ||
+ contains(value, index, 2, "WH"))) {
+ if (isVowel(charAt(value, index + 1))) {
+ //-- Wasserman should match Vasserman --//
+ result.append('A', 'F');
+ } else {
+ //-- need Uomo to match Womo --//
+ result.append('A');
+ }
+ index++;
+ } else if ((index == value.length() - 1 && isVowel(charAt(value, index - 1))) ||
+ contains(value, index - 1, 5, "EWSKI", "EWSKY", "OWSKI", "OWSKY") ||
+ contains(value, 0, 3, "SCH")) {
+ //-- Arnow should match Arnoff --//
+ result.appendAlternate('F');
+ index++;
+ } else if (contains(value, index, 4, "WICZ", "WITZ")) {
+ //-- Polish e.g. "filipowicz" --//
+ result.append("TS", "FX");
+ index += 4;
+ } else {
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'X' cases.
+ */
+ private int handleX(final String value, final DoubleMetaphoneResult result, int index) {
+ if (index == 0) {
+ result.append('S');
+ index++;
+ } else {
+ if (!((index == value.length() - 1) &&
+ (contains(value, index - 3, 3, "IAU", "EAU") ||
+ contains(value, index - 2, 2, "AU", "OU")))) {
+ //-- French e.g. breaux --//
+ result.append("KS");
+ }
+ index = contains(value, index + 1, 1, "C", "X") ? index + 2 : index + 1;
+ }
+ return index;
+ }
+
+ /**
+ * Handles 'Z' cases.
+ */
+ private int handleZ(final String value, final DoubleMetaphoneResult result, int index,
+ final boolean slavoGermanic) {
+ if (charAt(value, index + 1) == 'H') {
+ //-- Chinese pinyin e.g. "zhao" or Angelina "Zhang" --//
+ result.append('J');
+ index += 2;
+ } else {
+ if (contains(value, index + 1, 2, "ZO", "ZI", "ZA") ||
+ (slavoGermanic && (index > 0 && charAt(value, index - 1) != 'T'))) {
+ result.append("S", "TS");
+ } else {
+ result.append('S');
+ }
+ index = charAt(value, index + 1) == 'Z' ? index + 2 : index + 1;
+ }
+ return index;
+ }
+
+ //-- BEGIN CONDITIONS --//
+
+ /**
+ * Complex condition 0 for 'C'.
+ */
+ private boolean conditionC0(final String value, final int index) {
+ if (contains(value, index, 4, "CHIA")) {
+ return true;
+ } else if (index <= 1) {
+ return false;
+ } else if (isVowel(charAt(value, index - 2))) {
+ return false;
+ } else if (!contains(value, index - 1, 3, "ACH")) {
+ return false;
+ } else {
+ final char c = charAt(value, index + 2);
+ return (c != 'I' && c != 'E') ||
+ contains(value, index - 2, 6, "BACHER", "MACHER");
+ }
+ }
+
+ /**
+ * Complex condition 0 for 'CH'.
+ */
+ private boolean conditionCH0(final String value, final int index) {
+ if (index != 0) {
+ return false;
+ } else if (!contains(value, index + 1, 5, "HARAC", "HARIS") &&
+ !contains(value, index + 1, 3, "HOR", "HYM", "HIA", "HEM")) {
+ return false;
+ } else if (contains(value, 0, 5, "CHORE")) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Complex condition 1 for 'CH'.
+ */
+ private boolean conditionCH1(final String value, final int index) {
+ return ((contains(value, 0, 4, "VAN ", "VON ") || contains(value, 0, 3, "SCH")) ||
+ contains(value, index - 2, 6, "ORCHES", "ARCHIT", "ORCHID") ||
+ contains(value, index + 2, 1, "T", "S") ||
+ ((contains(value, index - 1, 1, "A", "O", "U", "E") || index == 0) &&
+ (contains(value, index + 2, 1, L_R_N_M_B_H_F_V_W_SPACE) || index + 1 == value.length() - 1)));
+ }
+
+ /**
+ * Complex condition 0 for 'L'.
+ */
+ private boolean conditionL0(final String value, final int index) {
+ if (index == value.length() - 3 &&
+ contains(value, index - 1, 4, "ILLO", "ILLA", "ALLE")) {
+ return true;
+ } else if ((contains(value, value.length() - 2, 2, "AS", "OS") ||
+ contains(value, value.length() - 1, 1, "A", "O")) &&
+ contains(value, index - 1, 4, "ALLE")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Complex condition 0 for 'M'.
+ */
+ private boolean conditionM0(final String value, final int index) {
+ if (charAt(value, index + 1) == 'M') {
+ return true;
+ }
+ return contains(value, index - 1, 3, "UMB") &&
+ ((index + 1) == value.length() - 1 || contains(value, index + 2, 2, "ER"));
+ }
+
+ //-- BEGIN HELPER FUNCTIONS --//
+
+ /**
+ * Determines whether or not a value is of slavo-germanic origin. A value is
+ * of slavo-germanic origin if it contians any of 'W', 'K', 'CZ', or 'WITZ'.
+ */
+ private boolean isSlavoGermanic(final String value) {
+ return value.indexOf('W') > -1 || value.indexOf('K') > -1 ||
+ value.indexOf("CZ") > -1 || value.indexOf("WITZ") > -1;
+ }
+
+ /**
+ * Determines whether or not a character is a vowel or not
+ */
+ private boolean isVowel(final char ch) {
+ return VOWELS.indexOf(ch) != -1;
+ }
+
+ /**
+ * Determines whether or not the value starts with a silent letter. It will
+ * return true
if the value starts with any of 'GN', 'KN',
+ * 'PN', 'WR' or 'PS'.
+ */
+ private boolean isSilentStart(final String value) {
+ boolean result = false;
+ for (final String element : SILENT_START) {
+ if (value.startsWith(element)) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Cleans the input.
+ */
+ private String cleanInput(String input) {
+ if (input == null) {
+ return null;
+ }
+ input = input.trim();
+ if (input.length() == 0) {
+ return null;
+ }
+ return input.toUpperCase(java.util.Locale.ENGLISH);
+ }
+
+ /*
+ * Gets the character at index index
if available, otherwise
+ * it returns Character.MIN_VALUE
so that there is some sort
+ * of a default.
+ */
+ protected char charAt(final String value, final int index) {
+ if (index < 0 || index >= value.length()) {
+ return Character.MIN_VALUE;
+ }
+ return value.charAt(index);
+ }
+
+ /*
+ * Determines whether value
contains any of the criteria starting at index start
and
+ * matching up to length length
.
+ */
+ protected static boolean contains(final String value, final int start, final int length,
+ final String... criteria) {
+ boolean result = false;
+ if (start >= 0 && start + length <= value.length()) {
+ final String target = value.substring(start, start + length);
+
+ for (final String element : criteria) {
+ if (target.equals(element)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ //-- BEGIN INNER CLASSES --//
+
+ /**
+ * Inner class for storing results, since there is the optional alternate encoding.
+ */
+ public class DoubleMetaphoneResult {
+
+ private final StringBuilder primary = new StringBuilder(getMaxCodeLen());
+ private final StringBuilder alternate = new StringBuilder(getMaxCodeLen());
+ private final int maxLength;
+
+ public DoubleMetaphoneResult(final int maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ public void append(final char value) {
+ appendPrimary(value);
+ appendAlternate(value);
+ }
+
+ public void append(final char primary, final char alternate) {
+ appendPrimary(primary);
+ appendAlternate(alternate);
+ }
+
+ public void appendPrimary(final char value) {
+ if (this.primary.length() < this.maxLength) {
+ this.primary.append(value);
+ }
+ }
+
+ public void appendAlternate(final char value) {
+ if (this.alternate.length() < this.maxLength) {
+ this.alternate.append(value);
+ }
+ }
+
+ public void append(final String value) {
+ appendPrimary(value);
+ appendAlternate(value);
+ }
+
+ public void append(final String primary, final String alternate) {
+ appendPrimary(primary);
+ appendAlternate(alternate);
+ }
+
+ public void appendPrimary(final String value) {
+ final int addChars = this.maxLength - this.primary.length();
+ if (value.length() <= addChars) {
+ this.primary.append(value);
+ } else {
+ this.primary.append(value.substring(0, addChars));
+ }
+ }
+
+ public void appendAlternate(final String value) {
+ final int addChars = this.maxLength - this.alternate.length();
+ if (value.length() <= addChars) {
+ this.alternate.append(value);
+ } else {
+ this.alternate.append(value.substring(0, addChars));
+ }
+ }
+
+ public String getPrimary() {
+ return this.primary.toString();
+ }
+
+ public String getAlternate() {
+ return this.alternate.toString();
+ }
+
+ public boolean isComplete() {
+ return this.primary.length() >= this.maxLength &&
+ this.alternate.length() >= this.maxLength;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/MatchRatingApproachEncoder.java b/src/main/java/org/apache/commons/codec/language/MatchRatingApproachEncoder.java
new file mode 100644
index 00000000..95858b43
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/MatchRatingApproachEncoder.java
@@ -0,0 +1,426 @@
+/*
+ * 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.commons.codec.language;
+
+import java.util.Locale;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Match Rating Approach Phonetic Algorithm Developed by Western Airlines in 1977.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see Wikipedia - Match Rating Approach
+ * @since 1.8
+ */
+public class MatchRatingApproachEncoder implements StringEncoder {
+
+ private static final String SPACE = " ";
+
+ private static final String EMPTY = "";
+
+ /**
+ * Constants used mainly for the min rating value.
+ */
+ private static final int ONE = 1, TWO = 2, THREE = 3, FOUR = 4, FIVE = 5, SIX = 6, SEVEN = 7, EIGHT = 8,
+ ELEVEN = 11, TWELVE = 12;
+
+ /**
+ * The plain letter equivalent of the accented letters.
+ */
+ private static final String PLAIN_ASCII = "AaEeIiOoUu" + // grave
+ "AaEeIiOoUuYy" + // acute
+ "AaEeIiOoUuYy" + // circumflex
+ "AaOoNn" + // tilde
+ "AaEeIiOoUuYy" + // umlaut
+ "Aa" + // ring
+ "Cc" + // cedilla
+ "OoUu"; // double acute
+
+ /**
+ * Unicode characters corresponding to various accented letters. For example: \u00DA is U acute etc...
+ */
+ private static final String UNICODE = "\u00C0\u00E0\u00C8\u00E8\u00CC\u00EC\u00D2\u00F2\u00D9\u00F9" +
+ "\u00C1\u00E1\u00C9\u00E9\u00CD\u00ED\u00D3\u00F3\u00DA\u00FA\u00DD\u00FD" +
+ "\u00C2\u00E2\u00CA\u00EA\u00CE\u00EE\u00D4\u00F4\u00DB\u00FB\u0176\u0177" +
+ "\u00C3\u00E3\u00D5\u00F5\u00D1\u00F1" +
+ "\u00C4\u00E4\u00CB\u00EB\u00CF\u00EF\u00D6\u00F6\u00DC\u00FC\u0178\u00FF" +
+ "\u00C5\u00E5" + "\u00C7\u00E7" + "\u0150\u0151\u0170\u0171";
+
+ private static final String[] DOUBLE_CONSONANT =
+ new String[] { "BB", "CC", "DD", "FF", "GG", "HH", "JJ", "KK", "LL", "MM", "NN", "PP", "QQ", "RR", "SS",
+ "TT", "VV", "WW", "XX", "YY", "ZZ" };
+
+ /**
+ * Cleans up a name: 1. Upper-cases everything 2. Removes some common punctuation 3. Removes accents 4. Removes any
+ * spaces.
+ *
+ *
API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param name
+ * The name to be cleaned
+ * @return The cleaned name
+ */
+ String cleanName(final String name) {
+ String upperName = name.toUpperCase(Locale.ENGLISH);
+
+ final String[] charsToTrim = { "\\-", "[&]", "\\'", "\\.", "[\\,]" };
+ for (final String str : charsToTrim) {
+ upperName = upperName.replaceAll(str, EMPTY);
+ }
+
+ upperName = removeAccents(upperName);
+ upperName = upperName.replaceAll("\\s+", EMPTY);
+
+ return upperName;
+ }
+
+ /**
+ * Encodes an Object using the Match Rating Approach algorithm. Method is here to satisfy the requirements of the
+ * Encoder interface Throws an EncoderException if input object is not of type java.lang.String.
+ *
+ * @param pObject
+ * Object to encode
+ * @return An object (or type java.lang.String) containing the Match Rating Approach code which corresponds to the
+ * String supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ */
+ @Override
+ public final Object encode(final Object pObject) throws EncoderException {
+ if (!(pObject instanceof String)) {
+ throw new EncoderException(
+ "Parameter supplied to Match Rating Approach encoder is not of type java.lang.String");
+ }
+ return encode((String) pObject);
+ }
+
+ /**
+ * Encodes a String using the Match Rating Approach (MRA) algorithm.
+ *
+ * @param name
+ * String object to encode
+ * @return The MRA code corresponding to the String supplied
+ */
+ @Override
+ public final String encode(String name) {
+ // Bulletproof for trivial input - NINO
+ if (name == null || EMPTY.equalsIgnoreCase(name) || SPACE.equalsIgnoreCase(name) || name.length() == 1) {
+ return EMPTY;
+ }
+
+ // Preprocessing
+ name = cleanName(name);
+
+ // BEGIN: Actual encoding part of the algorithm...
+ // 1. Delete all vowels unless the vowel begins the word
+ name = removeVowels(name);
+
+ // 2. Remove second consonant from any double consonant
+ name = removeDoubleConsonants(name);
+
+ // 3. Reduce codex to 6 letters by joining the first 3 and last 3 letters
+ name = getFirst3Last3(name);
+
+ return name;
+ }
+
+ /**
+ * Gets the first and last 3 letters of a name (if > 6 characters) Else just returns the name.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param name
+ * The string to get the substrings from
+ * @return Annexed first and last 3 letters of input word.
+ */
+ String getFirst3Last3(final String name) {
+ final int nameLength = name.length();
+
+ if (nameLength > SIX) {
+ final String firstThree = name.substring(0, THREE);
+ final String lastThree = name.substring(nameLength - THREE, nameLength);
+ return firstThree + lastThree;
+ } else {
+ return name;
+ }
+ }
+
+ /**
+ * Obtains the min rating of the length sum of the 2 names. In essence the larger the sum length the smaller the
+ * min rating. Values strictly from documentation.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param sumLength
+ * The length of 2 strings sent down
+ * @return The min rating value
+ */
+ int getMinRating(final int sumLength) {
+ int minRating = 0;
+
+ if (sumLength <= FOUR) {
+ minRating = FIVE;
+ } else if (sumLength >= FIVE && sumLength <= SEVEN) {
+ minRating = FOUR;
+ } else if (sumLength >= EIGHT && sumLength <= ELEVEN) {
+ minRating = THREE;
+ } else if (sumLength == TWELVE) {
+ minRating = TWO;
+ } else {
+ minRating = ONE; // docs said little here.
+ }
+
+ return minRating;
+ }
+
+ /**
+ * Determines if two names are homophonous via Match Rating Approach (MRA) algorithm. It should be noted that the
+ * strings are cleaned in the same way as {@link #encode(String)}.
+ *
+ * @param name1
+ * First of the 2 strings (names) to compare
+ * @param name2
+ * Second of the 2 names to compare
+ * @return true
if the encodings are identical false
otherwise.
+ */
+ public boolean isEncodeEquals(String name1, String name2) {
+ // Bulletproof for trivial input - NINO
+ if (name1 == null || EMPTY.equalsIgnoreCase(name1) || SPACE.equalsIgnoreCase(name1)) {
+ return false;
+ } else if (name2 == null || EMPTY.equalsIgnoreCase(name2) || SPACE.equalsIgnoreCase(name2)) {
+ return false;
+ } else if (name1.length() == 1 || name2.length() == 1) {
+ return false;
+ } else if (name1.equalsIgnoreCase(name2)) {
+ return true;
+ }
+
+ // Preprocessing
+ name1 = cleanName(name1);
+ name2 = cleanName(name2);
+
+ // Actual MRA Algorithm
+
+ // 1. Remove vowels
+ name1 = removeVowels(name1);
+ name2 = removeVowels(name2);
+
+ // 2. Remove double consonants
+ name1 = removeDoubleConsonants(name1);
+ name2 = removeDoubleConsonants(name2);
+
+ // 3. Reduce down to 3 letters
+ name1 = getFirst3Last3(name1);
+ name2 = getFirst3Last3(name2);
+
+ // 4. Check for length difference - if 3 or greater then no similarity
+ // comparison is done
+ if (Math.abs(name1.length() - name2.length()) >= THREE) {
+ return false;
+ }
+
+ // 5. Obtain the minimum rating value by calculating the length sum of the
+ // encoded Strings and sending it down.
+ final int sumLength = Math.abs(name1.length() + name2.length());
+ int minRating = 0;
+ minRating = getMinRating(sumLength);
+
+ // 6. Process the encoded Strings from left to right and remove any
+ // identical characters found from both Strings respectively.
+ final int count = leftToRightThenRightToLeftProcessing(name1, name2);
+
+ // 7. Each PNI item that has a similarity rating equal to or greater than
+ // the min is considered to be a good candidate match
+ return count >= minRating;
+
+ }
+
+ /**
+ * Determines if a letter is a vowel.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param letter
+ * The letter under investiagtion
+ * @return True if a vowel, else false
+ */
+ boolean isVowel(final String letter) {
+ return letter.equalsIgnoreCase("E") || letter.equalsIgnoreCase("A") || letter.equalsIgnoreCase("O") ||
+ letter.equalsIgnoreCase("I") || letter.equalsIgnoreCase("U");
+ }
+
+ /**
+ * Processes the names from left to right (first) then right to left removing identical letters in same positions.
+ * Then subtracts the longer string that remains from 6 and returns this.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param name1
+ * name2
+ * @return
+ */
+ int leftToRightThenRightToLeftProcessing(final String name1, final String name2) {
+ final char[] name1Char = name1.toCharArray();
+ final char[] name2Char = name2.toCharArray();
+
+ final int name1Size = name1.length() - 1;
+ final int name2Size = name2.length() - 1;
+
+ String name1LtRStart = EMPTY;
+ String name1LtREnd = EMPTY;
+
+ String name2RtLStart = EMPTY;
+ String name2RtLEnd = EMPTY;
+
+ for (int i = 0; i < name1Char.length; i++) {
+ if (i > name2Size) {
+ break;
+ }
+
+ name1LtRStart = name1.substring(i, i + 1);
+ name1LtREnd = name1.substring(name1Size - i, name1Size - i + 1);
+
+ name2RtLStart = name2.substring(i, i + 1);
+ name2RtLEnd = name2.substring(name2Size - i, name2Size - i + 1);
+
+ // Left to right...
+ if (name1LtRStart.equals(name2RtLStart)) {
+ name1Char[i] = ' ';
+ name2Char[i] = ' ';
+ }
+
+ // Right to left...
+ if (name1LtREnd.equals(name2RtLEnd)) {
+ name1Char[name1Size - i] = ' ';
+ name2Char[name2Size - i] = ' ';
+ }
+ }
+
+ // Char arrays -> string & remove extraneous space
+ final String strA = new String(name1Char).replaceAll("\\s+", EMPTY);
+ final String strB = new String(name2Char).replaceAll("\\s+", EMPTY);
+
+ // Final bit - subtract longest string from 6 and return this int value
+ if (strA.length() > strB.length()) {
+ return Math.abs(SIX - strA.length());
+ } else {
+ return Math.abs(SIX - strB.length());
+ }
+ }
+
+ /**
+ * Removes accented letters and replaces with non-accented ascii equivalent Case is preserved.
+ * http://www.codecodex.com/wiki/Remove_accent_from_letters_%28ex_.%C3%A9_to_e%29
+ *
+ * @param accentedWord
+ * The word that may have accents in it.
+ * @return De-accented word
+ */
+ String removeAccents(final String accentedWord) {
+ if (accentedWord == null) {
+ return null;
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ final int n = accentedWord.length();
+
+ for (int i = 0; i < n; i++) {
+ final char c = accentedWord.charAt(i);
+ final int pos = UNICODE.indexOf(c);
+ if (pos > -1) {
+ sb.append(PLAIN_ASCII.charAt(pos));
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Replaces any double consonant pair with the single letter equivalent.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param name
+ * String to have double consonants removed
+ * @return Single consonant word
+ */
+ String removeDoubleConsonants(final String name) {
+ String replacedName = name.toUpperCase();
+ for (final String dc : DOUBLE_CONSONANT) {
+ if (replacedName.contains(dc)) {
+ final String singleLetter = dc.substring(0, 1);
+ replacedName = replacedName.replace(dc, singleLetter);
+ }
+ }
+ return replacedName;
+ }
+
+ /**
+ * Deletes all vowels unless the vowel begins the word.
+ *
+ * API Usage
+ *
+ * Consider this method private, it is package protected for unit testing only.
+ *
+ *
+ * @param name
+ * The name to have vowels removed
+ * @return De-voweled word
+ */
+ String removeVowels(String name) {
+ // Extract first letter
+ final String firstLetter = name.substring(0, 1);
+
+ name = name.replaceAll("A", EMPTY);
+ name = name.replaceAll("E", EMPTY);
+ name = name.replaceAll("I", EMPTY);
+ name = name.replaceAll("O", EMPTY);
+ name = name.replaceAll("U", EMPTY);
+
+ name = name.replaceAll("\\s{2,}\\b", SPACE);
+
+ // return isVowel(firstLetter) ? (firstLetter + name) : name;
+ if (isVowel(firstLetter)) {
+ return firstLetter + name;
+ } else {
+ return name;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Metaphone.java b/src/main/java/org/apache/commons/codec/language/Metaphone.java
new file mode 100644
index 00000000..766eda8c
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Metaphone.java
@@ -0,0 +1,430 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Metaphone value.
+ *
+ * Initial Java implementation by William B. Brogden. December, 1997 .
+ * Permission given by wbrogden for code to be used anywhere.
+ *
+ * Hanging on the Metaphone by Lawrence Philips in Computer Language of Dec. 1990,
+ * p 39.
+ *
+ * Note, that this does not match the algorithm that ships with PHP, or the algorithm found in the Perl implementations:
+ *
+ *
+ *
+ * They have had undocumented changes from the originally published algorithm.
+ * For more information, see CODEC-57 .
+ *
+ * This class is conditionally thread-safe.
+ * The instance field {@link #maxCodeLen} is mutable {@link #setMaxCodeLen(int)}
+ * but is not volatile, and accesses are not synchronized.
+ * If an instance of the class is shared between threads, the caller needs to ensure that suitable synchronization
+ * is used to ensure safe publication of the value between threads, and must not invoke {@link #setMaxCodeLen(int)}
+ * after initial setup.
+ *
+ * @version $Id$
+ */
+public class Metaphone implements StringEncoder {
+
+ /**
+ * Five values in the English language
+ */
+ private static final String VOWELS = "AEIOU";
+
+ /**
+ * Variable used in Metaphone algorithm
+ */
+ private static final String FRONTV = "EIY";
+
+ /**
+ * Variable used in Metaphone algorithm
+ */
+ private static final String VARSON = "CSPTG";
+
+ /**
+ * The max code length for metaphone is 4
+ */
+ private int maxCodeLen = 4;
+
+ /**
+ * Creates an instance of the Metaphone encoder
+ */
+ public Metaphone() {
+ super();
+ }
+
+ /**
+ * Find the metaphone value of a String. This is similar to the
+ * soundex algorithm, but better at finding similar sounding words.
+ * All input is converted to upper case.
+ * Limitations: Input format is expected to be a single ASCII word
+ * with only characters in the A - Z range, no punctuation or numbers.
+ *
+ * @param txt String to find the metaphone code for
+ * @return A metaphone code corresponding to the String supplied
+ */
+ public String metaphone(final String txt) {
+ boolean hard = false;
+ int txtLength;
+ if (txt == null || (txtLength = txt.length()) == 0) {
+ return "";
+ }
+ // single character is itself
+ if (txtLength == 1) {
+ return txt.toUpperCase(java.util.Locale.ENGLISH);
+ }
+
+ final char[] inwd = txt.toUpperCase(java.util.Locale.ENGLISH).toCharArray();
+
+ final StringBuilder local = new StringBuilder(40); // manipulate
+ final StringBuilder code = new StringBuilder(10); // output
+ // handle initial 2 characters exceptions
+ switch(inwd[0]) {
+ case 'K':
+ case 'G':
+ case 'P': /* looking for KN, etc*/
+ if (inwd[1] == 'N') {
+ local.append(inwd, 1, inwd.length - 1);
+ } else {
+ local.append(inwd);
+ }
+ break;
+ case 'A': /* looking for AE */
+ if (inwd[1] == 'E') {
+ local.append(inwd, 1, inwd.length - 1);
+ } else {
+ local.append(inwd);
+ }
+ break;
+ case 'W': /* looking for WR or WH */
+ if (inwd[1] == 'R') { // WR -> R
+ local.append(inwd, 1, inwd.length - 1);
+ break;
+ }
+ if (inwd[1] == 'H') {
+ local.append(inwd, 1, inwd.length - 1);
+ local.setCharAt(0, 'W'); // WH -> W
+ } else {
+ local.append(inwd);
+ }
+ break;
+ case 'X': /* initial X becomes S */
+ inwd[0] = 'S';
+ local.append(inwd);
+ break;
+ default:
+ local.append(inwd);
+ } // now local has working string with initials fixed
+
+ final int wdsz = local.length();
+ int n = 0;
+
+ while (code.length() < this.getMaxCodeLen() &&
+ n < wdsz ) { // max code size of 4 works well
+ final char symb = local.charAt(n);
+ // remove duplicate letters except C
+ if (symb != 'C' && isPreviousChar( local, n, symb ) ) {
+ n++;
+ } else { // not dup
+ switch(symb) {
+ case 'A':
+ case 'E':
+ case 'I':
+ case 'O':
+ case 'U':
+ if (n == 0) {
+ code.append(symb);
+ }
+ break; // only use vowel if leading char
+ case 'B':
+ if ( isPreviousChar(local, n, 'M') &&
+ isLastChar(wdsz, n) ) { // B is silent if word ends in MB
+ break;
+ }
+ code.append(symb);
+ break;
+ case 'C': // lots of C special cases
+ /* discard if SCI, SCE or SCY */
+ if ( isPreviousChar(local, n, 'S') &&
+ !isLastChar(wdsz, n) &&
+ FRONTV.indexOf(local.charAt(n + 1)) >= 0 ) {
+ break;
+ }
+ if (regionMatch(local, n, "CIA")) { // "CIA" -> X
+ code.append('X');
+ break;
+ }
+ if (!isLastChar(wdsz, n) &&
+ FRONTV.indexOf(local.charAt(n + 1)) >= 0) {
+ code.append('S');
+ break; // CI,CE,CY -> S
+ }
+ if (isPreviousChar(local, n, 'S') &&
+ isNextChar(local, n, 'H') ) { // SCH->sk
+ code.append('K');
+ break;
+ }
+ if (isNextChar(local, n, 'H')) { // detect CH
+ if (n == 0 &&
+ wdsz >= 3 &&
+ isVowel(local,2) ) { // CH consonant -> K consonant
+ code.append('K');
+ } else {
+ code.append('X'); // CHvowel -> X
+ }
+ } else {
+ code.append('K');
+ }
+ break;
+ case 'D':
+ if (!isLastChar(wdsz, n + 1) &&
+ isNextChar(local, n, 'G') &&
+ FRONTV.indexOf(local.charAt(n + 2)) >= 0) { // DGE DGI DGY -> J
+ code.append('J'); n += 2;
+ } else {
+ code.append('T');
+ }
+ break;
+ case 'G': // GH silent at end or before consonant
+ if (isLastChar(wdsz, n + 1) &&
+ isNextChar(local, n, 'H')) {
+ break;
+ }
+ if (!isLastChar(wdsz, n + 1) &&
+ isNextChar(local,n,'H') &&
+ !isVowel(local,n+2)) {
+ break;
+ }
+ if (n > 0 &&
+ ( regionMatch(local, n, "GN") ||
+ regionMatch(local, n, "GNED") ) ) {
+ break; // silent G
+ }
+ if (isPreviousChar(local, n, 'G')) {
+ // NOTE: Given that duplicated chars are removed, I don't see how this can ever be true
+ hard = true;
+ } else {
+ hard = false;
+ }
+ if (!isLastChar(wdsz, n) &&
+ FRONTV.indexOf(local.charAt(n + 1)) >= 0 &&
+ !hard) {
+ code.append('J');
+ } else {
+ code.append('K');
+ }
+ break;
+ case 'H':
+ if (isLastChar(wdsz, n)) {
+ break; // terminal H
+ }
+ if (n > 0 &&
+ VARSON.indexOf(local.charAt(n - 1)) >= 0) {
+ break;
+ }
+ if (isVowel(local,n+1)) {
+ code.append('H'); // Hvowel
+ }
+ break;
+ case 'F':
+ case 'J':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'R':
+ code.append(symb);
+ break;
+ case 'K':
+ if (n > 0) { // not initial
+ if (!isPreviousChar(local, n, 'C')) {
+ code.append(symb);
+ }
+ } else {
+ code.append(symb); // initial K
+ }
+ break;
+ case 'P':
+ if (isNextChar(local,n,'H')) {
+ // PH -> F
+ code.append('F');
+ } else {
+ code.append(symb);
+ }
+ break;
+ case 'Q':
+ code.append('K');
+ break;
+ case 'S':
+ if (regionMatch(local,n,"SH") ||
+ regionMatch(local,n,"SIO") ||
+ regionMatch(local,n,"SIA")) {
+ code.append('X');
+ } else {
+ code.append('S');
+ }
+ break;
+ case 'T':
+ if (regionMatch(local,n,"TIA") ||
+ regionMatch(local,n,"TIO")) {
+ code.append('X');
+ break;
+ }
+ if (regionMatch(local,n,"TCH")) {
+ // Silent if in "TCH"
+ break;
+ }
+ // substitute numeral 0 for TH (resembles theta after all)
+ if (regionMatch(local,n,"TH")) {
+ code.append('0');
+ } else {
+ code.append('T');
+ }
+ break;
+ case 'V':
+ code.append('F'); break;
+ case 'W':
+ case 'Y': // silent if not followed by vowel
+ if (!isLastChar(wdsz,n) &&
+ isVowel(local,n+1)) {
+ code.append(symb);
+ }
+ break;
+ case 'X':
+ code.append('K');
+ code.append('S');
+ break;
+ case 'Z':
+ code.append('S');
+ break;
+ default:
+ // do nothing
+ break;
+ } // end switch
+ n++;
+ } // end else from symb != 'C'
+ if (code.length() > this.getMaxCodeLen()) {
+ code.setLength(this.getMaxCodeLen());
+ }
+ }
+ return code.toString();
+ }
+
+ private boolean isVowel(final StringBuilder string, final int index) {
+ return VOWELS.indexOf(string.charAt(index)) >= 0;
+ }
+
+ private boolean isPreviousChar(final StringBuilder string, final int index, final char c) {
+ boolean matches = false;
+ if( index > 0 &&
+ index < string.length() ) {
+ matches = string.charAt(index - 1) == c;
+ }
+ return matches;
+ }
+
+ private boolean isNextChar(final StringBuilder string, final int index, final char c) {
+ boolean matches = false;
+ if( index >= 0 &&
+ index < string.length() - 1 ) {
+ matches = string.charAt(index + 1) == c;
+ }
+ return matches;
+ }
+
+ private boolean regionMatch(final StringBuilder string, final int index, final String test) {
+ boolean matches = false;
+ if( index >= 0 &&
+ index + test.length() - 1 < string.length() ) {
+ final String substring = string.substring( index, index + test.length());
+ matches = substring.equals( test );
+ }
+ return matches;
+ }
+
+ private boolean isLastChar(final int wdsz, final int n) {
+ return n + 1 == wdsz;
+ }
+
+
+ /**
+ * Encodes an Object using the metaphone algorithm. This method
+ * is provided in order to satisfy the requirements of the
+ * Encoder interface, and will throw an EncoderException if the
+ * supplied object is not of type java.lang.String.
+ *
+ * @param obj Object to encode
+ * @return An object (or type java.lang.String) containing the
+ * metaphone code which corresponds to the String supplied.
+ * @throws EncoderException if the parameter supplied is not
+ * of type java.lang.String
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("Parameter supplied to Metaphone encode is not of type java.lang.String");
+ }
+ return metaphone((String) obj);
+ }
+
+ /**
+ * Encodes a String using the Metaphone algorithm.
+ *
+ * @param str String object to encode
+ * @return The metaphone code corresponding to the String supplied
+ */
+ @Override
+ public String encode(final String str) {
+ return metaphone(str);
+ }
+
+ /**
+ * Tests is the metaphones of two strings are identical.
+ *
+ * @param str1 First of two strings to compare
+ * @param str2 Second of two strings to compare
+ * @return true
if the metaphones of these strings are identical,
+ * false
otherwise.
+ */
+ public boolean isMetaphoneEqual(final String str1, final String str2) {
+ return metaphone(str1).equals(metaphone(str2));
+ }
+
+ /**
+ * Returns the maxCodeLen.
+ * @return int
+ */
+ public int getMaxCodeLen() { return this.maxCodeLen; }
+
+ /**
+ * Sets the maxCodeLen.
+ * @param maxCodeLen The maxCodeLen to set
+ */
+ public void setMaxCodeLen(final int maxCodeLen) { this.maxCodeLen = maxCodeLen; }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Nysiis.java b/src/main/java/org/apache/commons/codec/language/Nysiis.java
new file mode 100644
index 00000000..14c1505c
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Nysiis.java
@@ -0,0 +1,319 @@
+/*
+ * 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.commons.codec.language;
+
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a NYSIIS value. NYSIIS is an encoding used to relate similar names, but can also be used as a
+ * general purpose scheme to find word with similar phonemes.
+ *
+ * NYSIIS features an accuracy increase of 2.7% over the traditional Soundex algorithm.
+ *
+ * Algorithm description:
+ *
+ * 1. Transcode first characters of name
+ * 1a. MAC -> MCC
+ * 1b. KN -> NN
+ * 1c. K -> C
+ * 1d. PH -> FF
+ * 1e. PF -> FF
+ * 1f. SCH -> SSS
+ * 2. Transcode last characters of name
+ * 2a. EE, IE -> Y
+ * 2b. DT,RT,RD,NT,ND -> D
+ * 3. First character of key = first character of name
+ * 4. Transcode remaining characters by following these rules, incrementing by one character each time
+ * 4a. EV -> AF else A,E,I,O,U -> A
+ * 4b. Q -> G
+ * 4c. Z -> S
+ * 4d. M -> N
+ * 4e. KN -> N else K -> C
+ * 4f. SCH -> SSS
+ * 4g. PH -> FF
+ * 4h. H -> If previous or next is nonvowel, previous
+ * 4i. W -> If previous is vowel, previous
+ * 4j. Add current to key if current != last key character
+ * 5. If last character is S, remove it
+ * 6. If last characters are AY, replace with Y
+ * 7. If last character is A, remove it
+ * 8. Collapse all strings of repeated characters
+ * 9. Add original first character of name as first character of key
+ *
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see NYSIIS on Wikipedia
+ * @see NYSIIS on dropby.com
+ * @see Soundex
+ * @since 1.7
+ * @version $Id$
+ */
+public class Nysiis implements StringEncoder {
+
+ private static final char[] CHARS_A = new char[] { 'A' };
+ private static final char[] CHARS_AF = new char[] { 'A', 'F' };
+ private static final char[] CHARS_C = new char[] { 'C' };
+ private static final char[] CHARS_FF = new char[] { 'F', 'F' };
+ private static final char[] CHARS_G = new char[] { 'G' };
+ private static final char[] CHARS_N = new char[] { 'N' };
+ private static final char[] CHARS_NN = new char[] { 'N', 'N' };
+ private static final char[] CHARS_S = new char[] { 'S' };
+ private static final char[] CHARS_SSS = new char[] { 'S', 'S', 'S' };
+
+ private static final Pattern PAT_MAC = Pattern.compile("^MAC");
+ private static final Pattern PAT_KN = Pattern.compile("^KN");
+ private static final Pattern PAT_K = Pattern.compile("^K");
+ private static final Pattern PAT_PH_PF = Pattern.compile("^(PH|PF)");
+ private static final Pattern PAT_SCH = Pattern.compile("^SCH");
+ private static final Pattern PAT_EE_IE = Pattern.compile("(EE|IE)$");
+ private static final Pattern PAT_DT_ETC = Pattern.compile("(DT|RT|RD|NT|ND)$");
+
+ private static final char SPACE = ' ';
+ private static final int TRUE_LENGTH = 6;
+
+ /**
+ * Tests if the given character is a vowel.
+ *
+ * @param c
+ * the character to test
+ * @return true
if the character is a vowel, false
otherwise
+ */
+ private static boolean isVowel(final char c) {
+ return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
+ }
+
+ /**
+ * Transcodes the remaining parts of the String. The method operates on a sliding window, looking at 4 characters at
+ * a time: [i-1, i, i+1, i+2].
+ *
+ * @param prev
+ * the previous character
+ * @param curr
+ * the current character
+ * @param next
+ * the next character
+ * @param aNext
+ * the after next character
+ * @return a transcoded array of characters, starting from the current position
+ */
+ private static char[] transcodeRemaining(final char prev, final char curr, final char next, final char aNext) {
+ // 1. EV -> AF
+ if (curr == 'E' && next == 'V') {
+ return CHARS_AF;
+ }
+
+ // A, E, I, O, U -> A
+ if (isVowel(curr)) {
+ return CHARS_A;
+ }
+
+ // 2. Q -> G, Z -> S, M -> N
+ if (curr == 'Q') {
+ return CHARS_G;
+ } else if (curr == 'Z') {
+ return CHARS_S;
+ } else if (curr == 'M') {
+ return CHARS_N;
+ }
+
+ // 3. KN -> NN else K -> C
+ if (curr == 'K') {
+ if (next == 'N') {
+ return CHARS_NN;
+ } else {
+ return CHARS_C;
+ }
+ }
+
+ // 4. SCH -> SSS
+ if (curr == 'S' && next == 'C' && aNext == 'H') {
+ return CHARS_SSS;
+ }
+
+ // PH -> FF
+ if (curr == 'P' && next == 'H') {
+ return CHARS_FF;
+ }
+
+ // 5. H -> If previous or next is a non vowel, previous.
+ if (curr == 'H' && (!isVowel(prev) || !isVowel(next))) {
+ return new char[] { prev };
+ }
+
+ // 6. W -> If previous is vowel, previous.
+ if (curr == 'W' && isVowel(prev)) {
+ return new char[] { prev };
+ }
+
+ return new char[] { curr };
+ }
+
+ /** Indicates the strict mode. */
+ private final boolean strict;
+
+ /**
+ * Creates an instance of the {@link Nysiis} encoder with strict mode (original form),
+ * i.e. encoded strings have a maximum length of 6.
+ */
+ public Nysiis() {
+ this(true);
+ }
+
+ /**
+ * Create an instance of the {@link Nysiis} encoder with the specified strict mode:
+ *
+ *
+ * true
: encoded strings have a maximum length of 6
+ * false
: encoded strings may have arbitrary length
+ *
+ *
+ * @param strict
+ * the strict mode
+ */
+ public Nysiis(final boolean strict) {
+ this.strict = strict;
+ }
+
+ /**
+ * Encodes an Object using the NYSIIS algorithm. This method is provided in order to satisfy the requirements of the
+ * Encoder interface, and will throw an {@link EncoderException} if the supplied object is not of type
+ * {@link String}.
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (or a {@link String}) containing the NYSIIS code which corresponds to the given String.
+ * @throws EncoderException
+ * if the parameter supplied is not of a {@link String}
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("Parameter supplied to Nysiis encode is not of type java.lang.String");
+ }
+ return this.nysiis((String) obj);
+ }
+
+ /**
+ * Encodes a String using the NYSIIS algorithm.
+ *
+ * @param str
+ * A String object to encode
+ * @return A Nysiis code corresponding to the String supplied
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public String encode(final String str) {
+ return this.nysiis(str);
+ }
+
+ /**
+ * Indicates the strict mode for this {@link Nysiis} encoder.
+ *
+ * @return true
if the encoder is configured for strict mode, false
otherwise
+ */
+ public boolean isStrict() {
+ return this.strict;
+ }
+
+ /**
+ * Retrieves the NYSIIS code for a given String object.
+ *
+ * @param str
+ * String to encode using the NYSIIS algorithm
+ * @return A NYSIIS code for the String supplied
+ */
+ public String nysiis(String str) {
+ if (str == null) {
+ return null;
+ }
+
+ // Use the same clean rules as Soundex
+ str = SoundexUtils.clean(str);
+
+ if (str.length() == 0) {
+ return str;
+ }
+
+ // Translate first characters of name:
+ // MAC -> MCC, KN -> NN, K -> C, PH | PF -> FF, SCH -> SSS
+ str = PAT_MAC.matcher(str).replaceFirst("MCC");
+ str = PAT_KN.matcher(str).replaceFirst("NN");
+ str = PAT_K.matcher(str).replaceFirst("C");
+ str = PAT_PH_PF.matcher(str).replaceFirst("FF");
+ str = PAT_SCH.matcher(str).replaceFirst("SSS");
+
+ // Translate last characters of name:
+ // EE -> Y, IE -> Y, DT | RT | RD | NT | ND -> D
+ str = PAT_EE_IE.matcher(str).replaceFirst("Y");
+ str = PAT_DT_ETC.matcher(str).replaceFirst("D");
+
+ // First character of key = first character of name.
+ final StringBuilder key = new StringBuilder(str.length());
+ key.append(str.charAt(0));
+
+ // Transcode remaining characters, incrementing by one character each time
+ final char[] chars = str.toCharArray();
+ final int len = chars.length;
+
+ for (int i = 1; i < len; i++) {
+ final char next = i < len - 1 ? chars[i + 1] : SPACE;
+ final char aNext = i < len - 2 ? chars[i + 2] : SPACE;
+ final char[] transcoded = transcodeRemaining(chars[i - 1], chars[i], next, aNext);
+ System.arraycopy(transcoded, 0, chars, i, transcoded.length);
+
+ // only append the current char to the key if it is different from the last one
+ if (chars[i] != chars[i - 1]) {
+ key.append(chars[i]);
+ }
+ }
+
+ if (key.length() > 1) {
+ char lastChar = key.charAt(key.length() - 1);
+
+ // If last character is S, remove it.
+ if (lastChar == 'S') {
+ key.deleteCharAt(key.length() - 1);
+ lastChar = key.charAt(key.length() - 1);
+ }
+
+ if (key.length() > 2) {
+ final char last2Char = key.charAt(key.length() - 2);
+ // If last characters are AY, replace with Y.
+ if (last2Char == 'A' && lastChar == 'Y') {
+ key.deleteCharAt(key.length() - 2);
+ }
+ }
+
+ // If last character is A, remove it.
+ if (lastChar == 'A') {
+ key.deleteCharAt(key.length() - 1);
+ }
+ }
+
+ final String string = key.toString();
+ return this.isStrict() ? string.substring(0, Math.min(TRUE_LENGTH, string.length())) : string;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/RefinedSoundex.java b/src/main/java/org/apache/commons/codec/language/RefinedSoundex.java
new file mode 100644
index 00000000..22d6d9eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/RefinedSoundex.java
@@ -0,0 +1,205 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Refined Soundex value. A refined soundex code is
+ * optimized for spell checking words. Soundex method originally developed by
+ * Margaret Odell and Robert Russell .
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ */
+public class RefinedSoundex implements StringEncoder {
+
+ /**
+ * @since 1.4
+ */
+ public static final String US_ENGLISH_MAPPING_STRING = "01360240043788015936020505";
+
+ /**
+ * RefinedSoundex is *refined* for a number of reasons one being that the
+ * mappings have been altered. This implementation contains default
+ * mappings for US English.
+ */
+ private static final char[] US_ENGLISH_MAPPING = US_ENGLISH_MAPPING_STRING.toCharArray();
+
+ /**
+ * Every letter of the alphabet is "mapped" to a numerical value. This char
+ * array holds the values to which each letter is mapped. This
+ * implementation contains a default map for US_ENGLISH
+ */
+ private final char[] soundexMapping;
+
+ /**
+ * This static variable contains an instance of the RefinedSoundex using
+ * the US_ENGLISH mapping.
+ */
+ public static final RefinedSoundex US_ENGLISH = new RefinedSoundex();
+
+ /**
+ * Creates an instance of the RefinedSoundex object using the default US
+ * English mapping.
+ */
+ public RefinedSoundex() {
+ this.soundexMapping = US_ENGLISH_MAPPING;
+ }
+
+ /**
+ * Creates a refined soundex instance using a custom mapping. This
+ * constructor can be used to customize the mapping, and/or possibly
+ * provide an internationalized mapping for a non-Western character set.
+ *
+ * @param mapping
+ * Mapping array to use when finding the corresponding code for
+ * a given character
+ */
+ public RefinedSoundex(final char[] mapping) {
+ this.soundexMapping = new char[mapping.length];
+ System.arraycopy(mapping, 0, this.soundexMapping, 0, mapping.length);
+ }
+
+ /**
+ * Creates a refined Soundex instance using a custom mapping. This constructor can be used to customize the mapping,
+ * and/or possibly provide an internationalized mapping for a non-Western character set.
+ *
+ * @param mapping
+ * Mapping string to use when finding the corresponding code for a given character
+ * @since 1.4
+ */
+ public RefinedSoundex(final String mapping) {
+ this.soundexMapping = mapping.toCharArray();
+ }
+
+ /**
+ * Returns the number of characters in the two encoded Strings that are the
+ * same. This return value ranges from 0 to the length of the shortest
+ * encoded String: 0 indicates little or no similarity, and 4 out of 4 (for
+ * example) indicates strong similarity or identical values. For refined
+ * Soundex, the return value can be greater than 4.
+ *
+ * @param s1
+ * A String that will be encoded and compared.
+ * @param s2
+ * A String that will be encoded and compared.
+ * @return The number of characters in the two encoded Strings that are the
+ * same from 0 to to the length of the shortest encoded String.
+ *
+ * @see SoundexUtils#difference(StringEncoder,String,String)
+ * @see
+ * MS T-SQL DIFFERENCE
+ *
+ * @throws EncoderException
+ * if an error occurs encoding one of the strings
+ * @since 1.3
+ */
+ public int difference(final String s1, final String s2) throws EncoderException {
+ return SoundexUtils.difference(this, s1, s2);
+ }
+
+ /**
+ * Encodes an Object using the refined soundex algorithm. This method is
+ * provided in order to satisfy the requirements of the Encoder interface,
+ * and will throw an EncoderException if the supplied object is not of type
+ * java.lang.String.
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (or type java.lang.String) containing the refined
+ * soundex code which corresponds to the String supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("Parameter supplied to RefinedSoundex encode is not of type java.lang.String");
+ }
+ return soundex((String) obj);
+ }
+
+ /**
+ * Encodes a String using the refined soundex algorithm.
+ *
+ * @param str
+ * A String object to encode
+ * @return A Soundex code corresponding to the String supplied
+ */
+ @Override
+ public String encode(final String str) {
+ return soundex(str);
+ }
+
+ /**
+ * Returns the mapping code for a given character. The mapping codes are
+ * maintained in an internal char array named soundexMapping, and the
+ * default values of these mappings are US English.
+ *
+ * @param c
+ * char to get mapping for
+ * @return A character (really a numeral) to return for the given char
+ */
+ char getMappingCode(final char c) {
+ if (!Character.isLetter(c)) {
+ return 0;
+ }
+ return this.soundexMapping[Character.toUpperCase(c) - 'A'];
+ }
+
+ /**
+ * Retrieves the Refined Soundex code for a given String object.
+ *
+ * @param str
+ * String to encode using the Refined Soundex algorithm
+ * @return A soundex code for the String supplied
+ */
+ public String soundex(String str) {
+ if (str == null) {
+ return null;
+ }
+ str = SoundexUtils.clean(str);
+ if (str.length() == 0) {
+ return str;
+ }
+
+ final StringBuilder sBuf = new StringBuilder();
+ sBuf.append(str.charAt(0));
+
+ char last, current;
+ last = '*';
+
+ for (int i = 0; i < str.length(); i++) {
+
+ current = getMappingCode(str.charAt(i));
+ if (current == last) {
+ continue;
+ } else if (current != 0) {
+ sBuf.append(current);
+ }
+
+ last = current;
+
+ }
+
+ return sBuf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/Soundex.java b/src/main/java/org/apache/commons/codec/language/Soundex.java
new file mode 100644
index 00000000..2d4bbc1d
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/Soundex.java
@@ -0,0 +1,254 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes a string into a Soundex value. Soundex is an encoding used to relate similar names, but can also be used as a
+ * general purpose scheme to find word with similar phonemes.
+ *
+ * This class is thread-safe.
+ * Although not strictly immutable, the {@link #maxLength} field is not actually used.
+ *
+ * @version $Id$
+ */
+public class Soundex implements StringEncoder {
+
+ /**
+ * This is a default mapping of the 26 letters used in US English. A value of 0
for a letter position
+ * means do not encode.
+ *
+ * (This constant is provided as both an implementation convenience and to allow Javadoc to pick
+ * up the value for the constant values page.)
+ *
+ *
+ * @see #US_ENGLISH_MAPPING
+ */
+ public static final String US_ENGLISH_MAPPING_STRING = "0123012#02245501262301#202";
+
+ /**
+ * This is a default mapping of the 26 letters used in US English. A value of 0
for a letter position
+ * means do not encode.
+ *
+ * @see Soundex#Soundex(char[])
+ */
+ private static final char[] US_ENGLISH_MAPPING = US_ENGLISH_MAPPING_STRING.toCharArray();
+
+ /**
+ * An instance of Soundex using the US_ENGLISH_MAPPING mapping.
+ *
+ * @see #US_ENGLISH_MAPPING
+ */
+ public static final Soundex US_ENGLISH = new Soundex();
+
+ /**
+ * The maximum length of a Soundex code - Soundex codes are only four characters by definition.
+ *
+ * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0.
+ */
+ @Deprecated
+ private int maxLength = 4;
+
+ /**
+ * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each
+ * letter is mapped. This implementation contains a default map for US_ENGLISH
+ */
+ private final char[] soundexMapping;
+
+ /**
+ * Creates an instance using US_ENGLISH_MAPPING
+ *
+ * @see Soundex#Soundex(char[])
+ * @see Soundex#US_ENGLISH_MAPPING
+ */
+ public Soundex() {
+ this.soundexMapping = US_ENGLISH_MAPPING;
+ }
+
+ /**
+ * Creates a soundex instance using the given mapping. This constructor can be used to provide an internationalized
+ * mapping for a non-Western character set.
+ *
+ * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each
+ * letter is mapped. This implementation contains a default map for US_ENGLISH
+ *
+ * @param mapping
+ * Mapping array to use when finding the corresponding code for a given character
+ */
+ public Soundex(final char[] mapping) {
+ this.soundexMapping = new char[mapping.length];
+ System.arraycopy(mapping, 0, this.soundexMapping, 0, mapping.length);
+ }
+
+ /**
+ * Creates a refined soundex instance using a custom mapping. This constructor can be used to customize the mapping,
+ * and/or possibly provide an internationalized mapping for a non-Western character set.
+ *
+ * @param mapping
+ * Mapping string to use when finding the corresponding code for a given character
+ * @since 1.4
+ */
+ public Soundex(final String mapping) {
+ this.soundexMapping = mapping.toCharArray();
+ }
+
+ /**
+ * Encodes the Strings and returns the number of characters in the two encoded Strings that are the same. This
+ * return value ranges from 0 through 4: 0 indicates little or no similarity, and 4 indicates strong similarity or
+ * identical values.
+ *
+ * @param s1
+ * A String that will be encoded and compared.
+ * @param s2
+ * A String that will be encoded and compared.
+ * @return The number of characters in the two encoded Strings that are the same from 0 to 4.
+ *
+ * @see SoundexUtils#difference(StringEncoder,String,String)
+ * @see MS
+ * T-SQL DIFFERENCE
+ *
+ * @throws EncoderException
+ * if an error occurs encoding one of the strings
+ * @since 1.3
+ */
+ public int difference(final String s1, final String s2) throws EncoderException {
+ return SoundexUtils.difference(this, s1, s2);
+ }
+
+ /**
+ * Encodes an Object using the soundex algorithm. This method is provided in order to satisfy the requirements of
+ * the Encoder interface, and will throw an EncoderException if the supplied object is not of type java.lang.String.
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (or type java.lang.String) containing the soundex code which corresponds to the String
+ * supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type java.lang.String
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof String)) {
+ throw new EncoderException("Parameter supplied to Soundex encode is not of type java.lang.String");
+ }
+ return soundex((String) obj);
+ }
+
+ /**
+ * Encodes a String using the soundex algorithm.
+ *
+ * @param str
+ * A String object to encode
+ * @return A Soundex code corresponding to the String supplied
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ @Override
+ public String encode(final String str) {
+ return soundex(str);
+ }
+
+ /**
+ * Returns the maxLength. Standard Soundex
+ *
+ * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0.
+ * @return int
+ */
+ @Deprecated
+ public int getMaxLength() {
+ return this.maxLength;
+ }
+
+ /**
+ * Returns the soundex mapping.
+ *
+ * @return soundexMapping.
+ */
+ private char[] getSoundexMapping() {
+ return this.soundexMapping;
+ }
+
+ /**
+ * Maps the given upper-case character to its Soundex code.
+ *
+ * @param ch
+ * An upper-case character.
+ * @return A Soundex code.
+ * @throws IllegalArgumentException
+ * Thrown if ch
is not mapped.
+ */
+ private char map(final char ch) {
+ final int index = ch - 'A';
+ if (index < 0 || index >= this.getSoundexMapping().length) {
+ throw new IllegalArgumentException("The character is not mapped: " + ch);
+ }
+ return this.getSoundexMapping()[index];
+ }
+
+ /**
+ * Sets the maxLength.
+ *
+ * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0.
+ * @param maxLength
+ * The maxLength to set
+ */
+ @Deprecated
+ public void setMaxLength(final int maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ /**
+ * Retrieves the Soundex code for a given String object.
+ *
+ * @param str
+ * String to encode using the Soundex algorithm
+ * @return A soundex code for the String supplied
+ * @throws IllegalArgumentException
+ * if a character is not mapped
+ */
+ public String soundex(String str) {
+ if (str == null) {
+ return null;
+ }
+ str = SoundexUtils.clean(str);
+ if (str.length() == 0) {
+ return str;
+ }
+ final char out[] = {'0', '0', '0', '0'};
+ char last, mapped;
+ int incount = 1, count = 1;
+ out[0] = str.charAt(0);
+ // map() throws IllegalArgumentException
+ last = this.map(str.charAt(0));
+ while (incount < str.length() && count < out.length) {
+ mapped = this.map(str.charAt(incount++));
+ if (mapped == '0') {
+ last = mapped;
+ } else if (mapped != '#' && mapped != last) {
+ out[count++] = mapped;
+ last = mapped;
+ }
+ }
+ return new String(out);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/SoundexUtils.java b/src/main/java/org/apache/commons/codec/language/SoundexUtils.java
new file mode 100644
index 00000000..6409eb24
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/SoundexUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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.commons.codec.language;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Utility methods for {@link Soundex} and {@link RefinedSoundex} classes.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.3
+ */
+final class SoundexUtils {
+
+ /**
+ * Cleans up the input string before Soundex processing by only returning
+ * upper case letters.
+ *
+ * @param str
+ * The String to clean.
+ * @return A clean String.
+ */
+ static String clean(final String str) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ final int len = str.length();
+ final char[] chars = new char[len];
+ int count = 0;
+ for (int i = 0; i < len; i++) {
+ if (Character.isLetter(str.charAt(i))) {
+ chars[count++] = str.charAt(i);
+ }
+ }
+ if (count == len) {
+ return str.toUpperCase(java.util.Locale.ENGLISH);
+ }
+ return new String(chars, 0, count).toUpperCase(java.util.Locale.ENGLISH);
+ }
+
+ /**
+ * Encodes the Strings and returns the number of characters in the two
+ * encoded Strings that are the same.
+ *
+ * For Soundex, this return value ranges from 0 through 4: 0 indicates
+ * little or no similarity, and 4 indicates strong similarity or identical
+ * values.
+ * For refined Soundex, the return value can be greater than 4.
+ *
+ *
+ * @param encoder
+ * The encoder to use to encode the Strings.
+ * @param s1
+ * A String that will be encoded and compared.
+ * @param s2
+ * A String that will be encoded and compared.
+ * @return The number of characters in the two Soundex encoded Strings that
+ * are the same.
+ *
+ * @see #differenceEncoded(String,String)
+ * @see
+ * MS T-SQL DIFFERENCE
+ *
+ * @throws EncoderException
+ * if an error occurs encoding one of the strings
+ */
+ static int difference(final StringEncoder encoder, final String s1, final String s2) throws EncoderException {
+ return differenceEncoded(encoder.encode(s1), encoder.encode(s2));
+ }
+
+ /**
+ * Returns the number of characters in the two Soundex encoded Strings that
+ * are the same.
+ *
+ * For Soundex, this return value ranges from 0 through 4: 0 indicates
+ * little or no similarity, and 4 indicates strong similarity or identical
+ * values.
+ * For refined Soundex, the return value can be greater than 4.
+ *
+ *
+ * @param es1
+ * An encoded String.
+ * @param es2
+ * An encoded String.
+ * @return The number of characters in the two Soundex encoded Strings that
+ * are the same.
+ *
+ * @see
+ * MS T-SQL DIFFERENCE
+ */
+ static int differenceEncoded(final String es1, final String es2) {
+
+ if (es1 == null || es2 == null) {
+ return 0;
+ }
+ final int lengthToMatch = Math.min(es1.length(), es2.length());
+ int diff = 0;
+ for (int i = 0; i < lengthToMatch; i++) {
+ if (es1.charAt(i) == es2.charAt(i)) {
+ diff++;
+ }
+ }
+ return diff;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java b/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java
new file mode 100644
index 00000000..7339e7b4
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/BeiderMorseEncoder.java
@@ -0,0 +1,181 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Encodes strings into their Beider-Morse phonetic encoding.
+ *
+ * Beider-Morse phonetic encodings are optimised for family names. However, they may be useful for a wide range of
+ * words.
+ *
+ * This encoder is intentionally mutable to allow dynamic configuration through bean properties. As such, it is mutable,
+ * and may not be thread-safe. If you require a guaranteed thread-safe encoding then use {@link PhoneticEngine}
+ * directly.
+ *
+ * Encoding overview
+ *
+ * Beider-Morse phonetic encodings is a multi-step process. Firstly, a table of rules is consulted to guess what
+ * language the word comes from. For example, if it ends in "ault
" then it infers that the word is French.
+ * Next, the word is translated into a phonetic representation using a language-specific phonetics table. Some runs of
+ * letters can be pronounced in multiple ways, and a single run of letters may be potentially broken up into phonemes at
+ * different places, so this stage results in a set of possible language-specific phonetic representations. Lastly, this
+ * language-specific phonetic representation is processed by a table of rules that re-writes it phonetically taking into
+ * account systematic pronunciation differences between languages, to move it towards a pan-indo-european phonetic
+ * representation. Again, sometimes there are multiple ways this could be done and sometimes things that can be
+ * pronounced in several ways in the source language have only one way to represent them in this average phonetic
+ * language, so the result is again a set of phonetic spellings.
+ *
+ * Some names are treated as having multiple parts. This can be due to two things. Firstly, they may be hyphenated. In
+ * this case, each individual hyphenated word is encoded, and then these are combined end-to-end for the final encoding.
+ * Secondly, some names have standard prefixes, for example, "Mac/Mc
" in Scottish (English) names. As
+ * sometimes it is ambiguous whether the prefix is intended or is an accident of the spelling, the word is encoded once
+ * with the prefix and once without it. The resulting encoding contains one and then the other result.
+ *
+ * Encoding format
+ *
+ * Individual phonetic spellings of an input word are represented in upper- and lower-case roman characters. Where there
+ * are multiple possible phonetic representations, these are joined with a pipe (|
) character. If multiple
+ * hyphenated words where found, or if the word may contain a name prefix, each encoded word is placed in elipses and
+ * these blocks are then joined with hyphens. For example, "d'ortley
" has a possible prefix. The form
+ * without prefix encodes to "ortlaj|ortlej
", while the form with prefix encodes to "
+ * dortlaj|dortlej
". Thus, the full, combined encoding is "(ortlaj|ortlej)-(dortlaj|dortlej)
".
+ *
+ * The encoded forms are often quite a bit longer than the input strings. This is because a single input may have many
+ * potential phonetic interpretations. For example, "Renault
" encodes to "
+ * rYnDlt|rYnalt|rYnult|rinDlt|rinalt|rinult
". The APPROX
rules will tend to produce larger
+ * encodings as they consider a wider range of possible, approximate phonetic interpretations of the original word.
+ * Down-stream applications may wish to further process the encoding for indexing or lookup purposes, for example, by
+ * splitting on pipe (|
) and indexing under each of these alternatives.
+ *
+ * Note : this version of the Beider-Morse encoding is equivalent with v3.4 of the reference implementation.
+ *
+ * @see Beider-Morse Phonetic Matching
+ * @see Reference implementation
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public class BeiderMorseEncoder implements StringEncoder {
+ // Implementation note: This class is a spring-friendly facade to PhoneticEngine. It allows read/write configuration
+ // of an immutable PhoneticEngine instance that will be delegated to for the actual encoding.
+
+ // a cached object
+ private PhoneticEngine engine = new PhoneticEngine(NameType.GENERIC, RuleType.APPROX, true);
+
+ @Override
+ public Object encode(final Object source) throws EncoderException {
+ if (!(source instanceof String)) {
+ throw new EncoderException("BeiderMorseEncoder encode parameter is not of type String");
+ }
+ return encode((String) source);
+ }
+
+ @Override
+ public String encode(final String source) throws EncoderException {
+ if (source == null) {
+ return null;
+ }
+ return this.engine.encode(source);
+ }
+
+ /**
+ * Gets the name type currently in operation.
+ *
+ * @return the NameType currently being used
+ */
+ public NameType getNameType() {
+ return this.engine.getNameType();
+ }
+
+ /**
+ * Gets the rule type currently in operation.
+ *
+ * @return the RuleType currently being used
+ */
+ public RuleType getRuleType() {
+ return this.engine.getRuleType();
+ }
+
+ /**
+ * Discovers if multiple possible encodings are concatenated.
+ *
+ * @return true if multiple encodings are concatenated, false if just the first one is returned
+ */
+ public boolean isConcat() {
+ return this.engine.isConcat();
+ }
+
+ /**
+ * Sets how multiple possible phonetic encodings are combined.
+ *
+ * @param concat
+ * true if multiple encodings are to be combined with a '|', false if just the first one is
+ * to be considered
+ */
+ public void setConcat(final boolean concat) {
+ this.engine = new PhoneticEngine(this.engine.getNameType(),
+ this.engine.getRuleType(),
+ concat,
+ this.engine.getMaxPhonemes());
+ }
+
+ /**
+ * Sets the type of name. Use {@link NameType#GENERIC} unless you specifically want phonetic encodings
+ * optimized for Ashkenazi or Sephardic Jewish family names.
+ *
+ * @param nameType
+ * the NameType in use
+ */
+ public void setNameType(final NameType nameType) {
+ this.engine = new PhoneticEngine(nameType,
+ this.engine.getRuleType(),
+ this.engine.isConcat(),
+ this.engine.getMaxPhonemes());
+ }
+
+ /**
+ * Sets the rule type to apply. This will widen or narrow the range of phonetic encodings considered.
+ *
+ * @param ruleType
+ * {@link RuleType#APPROX} or {@link RuleType#EXACT} for approximate or exact phonetic matches
+ */
+ public void setRuleType(final RuleType ruleType) {
+ this.engine = new PhoneticEngine(this.engine.getNameType(),
+ ruleType,
+ this.engine.isConcat(),
+ this.engine.getMaxPhonemes());
+ }
+
+ /**
+ * Sets the number of maximum of phonemes that shall be considered by the engine.
+ *
+ * @param maxPhonemes
+ * the maximum number of phonemes returned by the engine
+ * @since 1.7
+ */
+ public void setMaxPhonemes(final int maxPhonemes) {
+ this.engine = new PhoneticEngine(this.engine.getNameType(),
+ this.engine.getRuleType(),
+ this.engine.isConcat(),
+ maxPhonemes);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/Lang.java b/src/main/java/org/apache/commons/codec/language/bm/Lang.java
new file mode 100644
index 00000000..a7ebba6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/Lang.java
@@ -0,0 +1,231 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Language guessing utility.
+ *
+ * This class encapsulates rules used to guess the possible languages that a word originates from. This is
+ * done by reference to a whole series of rules distributed in resource files.
+ *
+ * Instances of this class are typically managed through the static factory method instance().
+ * Unless you are developing your own language guessing rules, you will not need to interact with this class directly.
+ *
+ * This class is intended to be immutable and thread-safe.
+ *
+ * Lang resources
+ *
+ * Language guessing rules are typically loaded from resource files. These are UTF-8 encoded text files.
+ * They are systematically named following the pattern:
+ *
org/apache/commons/codec/language/bm/lang.txt
+ * The format of these resources is the following:
+ *
+ * Rules: whitespace separated strings.
+ * There should be 3 columns to each row, and these will be interpreted as:
+ *
+ * pattern: a regular expression.
+ * languages: a '+'-separated list of languages.
+ * acceptOnMatch: 'true' or 'false' indicating if a match rules in or rules out the language.
+ *
+ *
+ * End-of-line comments: Any occurrence of '//' will cause all text following on that line to be
+ * discarded as a comment.
+ * Multi-line comments: Any line starting with '/*' will start multi-line commenting mode.
+ * This will skip all content until a line ending in '*' and '/' is found.
+ * Blank lines: All blank lines will be skipped.
+ *
+ *
+ * Port of lang.php
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public class Lang {
+ // Implementation note: This class is divided into two sections. The first part is a static factory interface that
+ // exposes the LANGUAGE_RULES_RN resource as a Lang instance. The second part is the Lang instance methods that
+ // encapsulate a particular language-guessing rule table and the language guessing itself.
+ //
+ // It may make sense in the future to expose the private constructor to allow power users to build custom language-
+ // guessing rules, perhaps by marking it protected and allowing sub-classing. However, the vast majority of users
+ // should be strongly encouraged to use the static factory instance
method to get their Lang instances.
+
+ private static final class LangRule {
+ private final boolean acceptOnMatch;
+ private final Set languages;
+ private final Pattern pattern;
+
+ private LangRule(final Pattern pattern, final Set languages, final boolean acceptOnMatch) {
+ this.pattern = pattern;
+ this.languages = languages;
+ this.acceptOnMatch = acceptOnMatch;
+ }
+
+ public boolean matches(final String txt) {
+ return this.pattern.matcher(txt).find();
+ }
+ }
+
+ private static final Map Langs = new EnumMap(NameType.class);
+
+ private static final String LANGUAGE_RULES_RN = "org/apache/commons/codec/language/bm/%s_lang.txt";
+
+ static {
+ for (final NameType s : NameType.values()) {
+ Langs.put(s, loadFromResource(String.format(LANGUAGE_RULES_RN, s.getName()), Languages.getInstance(s)));
+ }
+ }
+
+ /**
+ * Gets a Lang instance for one of the supported NameTypes.
+ *
+ * @param nameType
+ * the NameType to look up
+ * @return a Lang encapsulating the language guessing rules for that name type
+ */
+ public static Lang instance(final NameType nameType) {
+ return Langs.get(nameType);
+ }
+
+ /**
+ * Loads language rules from a resource.
+ *
+ * In normal use, you will obtain instances of Lang through the {@link #instance(NameType)} method.
+ * You will only need to call this yourself if you are developing custom language mapping rules.
+ *
+ * @param languageRulesResourceName
+ * the fully-qualified resource name to load
+ * @param languages
+ * the languages that these rules will support
+ * @return a Lang encapsulating the loaded language-guessing rules.
+ */
+ public static Lang loadFromResource(final String languageRulesResourceName, final Languages languages) {
+ final List rules = new ArrayList();
+ final InputStream lRulesIS = Lang.class.getClassLoader().getResourceAsStream(languageRulesResourceName);
+
+ if (lRulesIS == null) {
+ throw new IllegalStateException("Unable to resolve required resource:" + LANGUAGE_RULES_RN);
+ }
+
+ final Scanner scanner = new Scanner(lRulesIS, ResourceConstants.ENCODING);
+ try {
+ boolean inExtendedComment = false;
+ while (scanner.hasNextLine()) {
+ final String rawLine = scanner.nextLine();
+ String line = rawLine;
+ if (inExtendedComment) {
+ // check for closing comment marker, otherwise discard doc comment line
+ if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
+ inExtendedComment = false;
+ }
+ } else {
+ if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
+ inExtendedComment = true;
+ } else {
+ // discard comments
+ final int cmtI = line.indexOf(ResourceConstants.CMT);
+ if (cmtI >= 0) {
+ line = line.substring(0, cmtI);
+ }
+
+ // trim leading-trailing whitespace
+ line = line.trim();
+
+ if (line.length() == 0) {
+ continue; // empty lines can be safely skipped
+ }
+
+ // split it up
+ final String[] parts = line.split("\\s+");
+
+ if (parts.length != 3) {
+ throw new IllegalArgumentException("Malformed line '" + rawLine +
+ "' in language resource '" + languageRulesResourceName + "'");
+ }
+
+ final Pattern pattern = Pattern.compile(parts[0]);
+ final String[] langs = parts[1].split("\\+");
+ final boolean accept = parts[2].equals("true");
+
+ rules.add(new LangRule(pattern, new HashSet(Arrays.asList(langs)), accept));
+ }
+ }
+ }
+ } finally {
+ scanner.close();
+ }
+ return new Lang(rules, languages);
+ }
+
+ private final Languages languages;
+ private final List rules;
+
+ private Lang(final List rules, final Languages languages) {
+ this.rules = Collections.unmodifiableList(rules);
+ this.languages = languages;
+ }
+
+ /**
+ * Guesses the language of a word.
+ *
+ * @param text
+ * the word
+ * @return the language that the word originates from or {@link Languages#ANY} if there was no unique match
+ */
+ public String guessLanguage(final String text) {
+ final Languages.LanguageSet ls = guessLanguages(text);
+ return ls.isSingleton() ? ls.getAny() : Languages.ANY;
+ }
+
+ /**
+ * Guesses the languages of a word.
+ *
+ * @param input
+ * the word
+ * @return a Set of Strings of language names that are potential matches for the input word
+ */
+ public Languages.LanguageSet guessLanguages(final String input) {
+ final String text = input.toLowerCase(Locale.ENGLISH);
+
+ final Set langs = new HashSet(this.languages.getLanguages());
+ for (final LangRule rule : this.rules) {
+ if (rule.matches(text)) {
+ if (rule.acceptOnMatch) {
+ langs.retainAll(rule.languages);
+ } else {
+ langs.removeAll(rule.languages);
+ }
+ }
+ }
+
+ final Languages.LanguageSet ls = Languages.LanguageSet.from(langs);
+ return ls.equals(Languages.NO_LANGUAGES) ? Languages.ANY_LANGUAGE : ls;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/Languages.java b/src/main/java/org/apache/commons/codec/language/bm/Languages.java
new file mode 100644
index 00000000..e092c15c
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/Languages.java
@@ -0,0 +1,295 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Language codes.
+ *
+ * Language codes are typically loaded from resource files. These are UTF-8 encoded text files. They are
+ * systematically named following the pattern:
+ *
org/apache/commons/codec/language/bm/${{@link NameType#getName()} languages.txt
+ *
+ * The format of these resources is the following:
+ *
+ * Language: a single string containing no whitespace
+ * End-of-line comments: Any occurrence of '//' will cause all text following on that line to be
+ * discarded as a comment.
+ * Multi-line comments: Any line starting with '/*' will start multi-line commenting mode.
+ * This will skip all content until a line ending in '*' and '/' is found.
+ * Blank lines: All blank lines will be skipped.
+ *
+ *
+ * Ported from language.php
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public class Languages {
+ // Implementation note: This class is divided into two sections. The first part is a static factory interface that
+ // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in NameType.* as a list of supported
+ // languages, and a second part that provides instance methods for accessing this set for supported languages.
+
+ /**
+ * A set of languages.
+ */
+ public static abstract class LanguageSet {
+
+ public static LanguageSet from(final Set langs) {
+ return langs.isEmpty() ? NO_LANGUAGES : new SomeLanguages(langs);
+ }
+
+ public abstract boolean contains(String language);
+
+ public abstract String getAny();
+
+ public abstract boolean isEmpty();
+
+ public abstract boolean isSingleton();
+
+ public abstract LanguageSet restrictTo(LanguageSet other);
+
+ abstract LanguageSet merge(LanguageSet other);
+ }
+
+ /**
+ * Some languages, explicitly enumerated.
+ */
+ public static final class SomeLanguages extends LanguageSet {
+ private final Set languages;
+
+ private SomeLanguages(final Set languages) {
+ this.languages = Collections.unmodifiableSet(languages);
+ }
+
+ @Override
+ public boolean contains(final String language) {
+ return this.languages.contains(language);
+ }
+
+ @Override
+ public String getAny() {
+ return this.languages.iterator().next();
+ }
+
+ public Set getLanguages() {
+ return this.languages;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.languages.isEmpty();
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return this.languages.size() == 1;
+ }
+
+ @Override
+ public LanguageSet restrictTo(final LanguageSet other) {
+ if (other == NO_LANGUAGES) {
+ return other;
+ } else if (other == ANY_LANGUAGE) {
+ return this;
+ } else {
+ final SomeLanguages sl = (SomeLanguages) other;
+ final Set ls = new HashSet(Math.min(languages.size(), sl.languages.size()));
+ for (String lang : languages) {
+ if (sl.languages.contains(lang)) {
+ ls.add(lang);
+ }
+ }
+ return from(ls);
+ }
+ }
+
+ @Override
+ public LanguageSet merge(final LanguageSet other) {
+ if (other == NO_LANGUAGES) {
+ return this;
+ } else if (other == ANY_LANGUAGE) {
+ return other;
+ } else {
+ final SomeLanguages sl = (SomeLanguages) other;
+ final Set ls = new HashSet(languages);
+ for (String lang : sl.languages) {
+ ls.add(lang);
+ }
+ return from(ls);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Languages(" + languages.toString() + ")";
+ }
+
+ }
+
+ public static final String ANY = "any";
+
+ private static final Map LANGUAGES = new EnumMap(NameType.class);
+
+ static {
+ for (final NameType s : NameType.values()) {
+ LANGUAGES.put(s, getInstance(langResourceName(s)));
+ }
+ }
+
+ public static Languages getInstance(final NameType nameType) {
+ return LANGUAGES.get(nameType);
+ }
+
+ public static Languages getInstance(final String languagesResourceName) {
+ // read languages list
+ final Set ls = new HashSet();
+ final InputStream langIS = Languages.class.getClassLoader().getResourceAsStream(languagesResourceName);
+
+ if (langIS == null) {
+ throw new IllegalArgumentException("Unable to resolve required resource: " + languagesResourceName);
+ }
+
+ final Scanner lsScanner = new Scanner(langIS, ResourceConstants.ENCODING);
+ try {
+ boolean inExtendedComment = false;
+ while (lsScanner.hasNextLine()) {
+ final String line = lsScanner.nextLine().trim();
+ if (inExtendedComment) {
+ if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
+ inExtendedComment = false;
+ }
+ } else {
+ if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
+ inExtendedComment = true;
+ } else if (line.length() > 0) {
+ ls.add(line);
+ }
+ }
+ }
+ } finally {
+ lsScanner.close();
+ }
+
+ return new Languages(Collections.unmodifiableSet(ls));
+ }
+
+ private static String langResourceName(final NameType nameType) {
+ return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
+ }
+
+ private final Set languages;
+
+ /**
+ * No languages at all.
+ */
+ public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
+ @Override
+ public boolean contains(final String language) {
+ return false;
+ }
+
+ @Override
+ public String getAny() {
+ throw new NoSuchElementException("Can't fetch any language from the empty language set.");
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return false;
+ }
+
+ @Override
+ public LanguageSet restrictTo(final LanguageSet other) {
+ return this;
+ }
+
+ @Override
+ public LanguageSet merge(final LanguageSet other) {
+ return other;
+ }
+
+ @Override
+ public String toString() {
+ return "NO_LANGUAGES";
+ }
+ };
+
+ /**
+ * Any/all languages.
+ */
+ public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
+ @Override
+ public boolean contains(final String language) {
+ return true;
+ }
+
+ @Override
+ public String getAny() {
+ throw new NoSuchElementException("Can't fetch any language from the any language set.");
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return false;
+ }
+
+ @Override
+ public LanguageSet restrictTo(final LanguageSet other) {
+ return other;
+ }
+
+ @Override
+ public LanguageSet merge(final LanguageSet other) {
+ return other;
+ }
+
+ @Override
+ public String toString() {
+ return "ANY_LANGUAGE";
+ }
+ };
+
+ private Languages(final Set languages) {
+ this.languages = languages;
+ }
+
+ public Set getLanguages() {
+ return this.languages;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/NameType.java b/src/main/java/org/apache/commons/codec/language/bm/NameType.java
new file mode 100644
index 00000000..004b2cf2
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/NameType.java
@@ -0,0 +1,53 @@
+/*
+ * 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.commons.codec.language.bm;
+
+/**
+ * Supported types of names. Unless you are matching particular family names, use {@link #GENERIC}. The
+ * GENERIC
NameType should work reasonably well for non-name words. The other encodings are
+ * specifically tuned to family names, and may not work well at all for general text.
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public enum NameType {
+
+ /** Ashkenazi family names */
+ ASHKENAZI("ash"),
+
+ /** Generic names and words */
+ GENERIC("gen"),
+
+ /** Sephardic family names */
+ SEPHARDIC("sep");
+
+ private final String name;
+
+ NameType(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the short version of the name type.
+ *
+ * @return the NameType short string
+ */
+ public String getName() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java b/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java
new file mode 100644
index 00000000..490757be
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/PhoneticEngine.java
@@ -0,0 +1,529 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.codec.language.bm.Languages.LanguageSet;
+import org.apache.commons.codec.language.bm.Rule.Phoneme;
+
+/**
+ * Converts words into potential phonetic representations.
+ *
+ * This is a two-stage process. Firstly, the word is converted into a phonetic representation that takes
+ * into account the likely source language. Next, this phonetic representation is converted into a
+ * pan-European 'average' representation, allowing comparison between different versions of essentially
+ * the same word from different languages.
+ *
+ * This class is intentionally immutable and thread-safe.
+ * If you wish to alter the settings for a PhoneticEngine, you
+ * must make a new one with the updated settings.
+ *
+ * Ported from phoneticengine.php
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public class PhoneticEngine {
+
+ /**
+ * Utility for manipulating a set of phonemes as they are being built up. Not intended for use outside
+ * this package, and probably not outside the {@link PhoneticEngine} class.
+ *
+ * @since 1.6
+ */
+ static final class PhonemeBuilder {
+
+ /**
+ * An empty builder where all phonemes must come from some set of languages. This will contain a single
+ * phoneme of zero characters. This can then be appended to. This should be the only way to create a new
+ * phoneme from scratch.
+ *
+ * @param languages the set of languages
+ * @return a new, empty phoneme builder
+ */
+ public static PhonemeBuilder empty(final Languages.LanguageSet languages) {
+ return new PhonemeBuilder(new Rule.Phoneme("", languages));
+ }
+
+ private final Set phonemes;
+
+ private PhonemeBuilder(final Rule.Phoneme phoneme) {
+ this.phonemes = new LinkedHashSet();
+ this.phonemes.add(phoneme);
+ }
+
+ private PhonemeBuilder(final Set phonemes) {
+ this.phonemes = phonemes;
+ }
+
+ /**
+ * Creates a new phoneme builder containing all phonemes in this one extended by str
.
+ *
+ * @param str the characters to append to the phonemes
+ */
+ public void append(final CharSequence str) {
+ for (final Rule.Phoneme ph : this.phonemes) {
+ ph.append(str);
+ }
+ }
+
+ /**
+ * Applies the given phoneme expression to all phonemes in this phoneme builder.
+ *
+ * This will lengthen phonemes that have compatible language sets to the expression, and drop those that are
+ * incompatible.
+ *
+ * @param phonemeExpr the expression to apply
+ * @param maxPhonemes the maximum number of phonemes to build up
+ */
+ public void apply(final Rule.PhonemeExpr phonemeExpr, final int maxPhonemes) {
+ final Set newPhonemes = new LinkedHashSet(maxPhonemes);
+
+ EXPR: for (final Rule.Phoneme left : this.phonemes) {
+ for (final Rule.Phoneme right : phonemeExpr.getPhonemes()) {
+ final LanguageSet languages = left.getLanguages().restrictTo(right.getLanguages());
+ if (!languages.isEmpty()) {
+ final Rule.Phoneme join = new Phoneme(left, right, languages);
+ if (newPhonemes.size() < maxPhonemes) {
+ newPhonemes.add(join);
+ if (newPhonemes.size() >= maxPhonemes) {
+ break EXPR;
+ }
+ }
+ }
+ }
+ }
+
+ this.phonemes.clear();
+ this.phonemes.addAll(newPhonemes);
+ }
+
+ /**
+ * Gets underlying phoneme set. Please don't mutate.
+ *
+ * @return the phoneme set
+ */
+ public Set getPhonemes() {
+ return this.phonemes;
+ }
+
+ /**
+ * Stringifies the phoneme set. This produces a single string of the strings of each phoneme,
+ * joined with a pipe. This is explicitly provided in place of toString as it is a potentially
+ * expensive operation, which should be avoided when debugging.
+ *
+ * @return the stringified phoneme set
+ */
+ public String makeString() {
+ final StringBuilder sb = new StringBuilder();
+
+ for (final Rule.Phoneme ph : this.phonemes) {
+ if (sb.length() > 0) {
+ sb.append("|");
+ }
+ sb.append(ph.getPhonemeText());
+ }
+
+ return sb.toString();
+ }
+ }
+
+ /**
+ * A function closure capturing the application of a list of rules to an input sequence at a particular offset.
+ * After invocation, the values i
and found
are updated. i
points to the
+ * index of the next char in input
that must be processed next (the input up to that index having been
+ * processed already), and found
indicates if a matching rule was found or not. In the case where a
+ * matching rule was found, phonemeBuilder
is replaced with a new builder containing the phonemes
+ * updated by the matching rule.
+ *
+ * Although this class is not thread-safe (it has mutable unprotected fields), it is not shared between threads
+ * as it is constructed as needed by the calling methods.
+ * @since 1.6
+ */
+ private static final class RulesApplication {
+ private final Map> finalRules;
+ private final CharSequence input;
+
+ private PhonemeBuilder phonemeBuilder;
+ private int i;
+ private final int maxPhonemes;
+ private boolean found;
+
+ public RulesApplication(final Map> finalRules, final CharSequence input,
+ final PhonemeBuilder phonemeBuilder, final int i, final int maxPhonemes) {
+ if (finalRules == null) {
+ throw new NullPointerException("The finalRules argument must not be null");
+ }
+ this.finalRules = finalRules;
+ this.phonemeBuilder = phonemeBuilder;
+ this.input = input;
+ this.i = i;
+ this.maxPhonemes = maxPhonemes;
+ }
+
+ public int getI() {
+ return this.i;
+ }
+
+ public PhonemeBuilder getPhonemeBuilder() {
+ return this.phonemeBuilder;
+ }
+
+ /**
+ * Invokes the rules. Loops over the rules list, stopping at the first one that has a matching context
+ * and pattern. Then applies this rule to the phoneme builder to produce updated phonemes. If there was no
+ * match, i
is advanced one and the character is silently dropped from the phonetic spelling.
+ *
+ * @return this
+ */
+ public RulesApplication invoke() {
+ this.found = false;
+ int patternLength = 1;
+ final List rules = this.finalRules.get(input.subSequence(i, i+patternLength));
+ if (rules != null) {
+ for (final Rule rule : rules) {
+ final String pattern = rule.getPattern();
+ patternLength = pattern.length();
+ if (rule.patternAndContextMatches(this.input, this.i)) {
+ this.phonemeBuilder.apply(rule.getPhoneme(), maxPhonemes);
+ this.found = true;
+ break;
+ }
+ }
+ }
+
+ if (!this.found) {
+ patternLength = 1;
+ }
+
+ this.i += patternLength;
+ return this;
+ }
+
+ public boolean isFound() {
+ return this.found;
+ }
+ }
+
+ private static final Map> NAME_PREFIXES = new EnumMap>(NameType.class);
+
+ static {
+ NAME_PREFIXES.put(NameType.ASHKENAZI,
+ Collections.unmodifiableSet(
+ new HashSet(Arrays.asList("bar", "ben", "da", "de", "van", "von"))));
+ NAME_PREFIXES.put(NameType.SEPHARDIC,
+ Collections.unmodifiableSet(
+ new HashSet(Arrays.asList("al", "el", "da", "dal", "de", "del", "dela", "de la",
+ "della", "des", "di", "do", "dos", "du", "van", "von"))));
+ NAME_PREFIXES.put(NameType.GENERIC,
+ Collections.unmodifiableSet(
+ new HashSet(Arrays.asList("da", "dal", "de", "del", "dela", "de la", "della",
+ "des", "di", "do", "dos", "du", "van", "von"))));
+ }
+
+ /**
+ * Joins some strings with an internal separator.
+ * @param strings Strings to join
+ * @param sep String to separate them with
+ * @return a single String consisting of each element of strings
interleaved by sep
+ */
+ private static String join(final Iterable strings, final String sep) {
+ final StringBuilder sb = new StringBuilder();
+ final Iterator si = strings.iterator();
+ if (si.hasNext()) {
+ sb.append(si.next());
+ }
+ while (si.hasNext()) {
+ sb.append(sep).append(si.next());
+ }
+
+ return sb.toString();
+ }
+
+ private static final int DEFAULT_MAX_PHONEMES = 20;
+
+ private final Lang lang;
+
+ private final NameType nameType;
+
+ private final RuleType ruleType;
+
+ private final boolean concat;
+
+ private final int maxPhonemes;
+
+ /**
+ * Generates a new, fully-configured phonetic engine.
+ *
+ * @param nameType
+ * the type of names it will use
+ * @param ruleType
+ * the type of rules it will apply
+ * @param concat
+ * if it will concatenate multiple encodings
+ */
+ public PhoneticEngine(final NameType nameType, final RuleType ruleType, final boolean concat) {
+ this(nameType, ruleType, concat, DEFAULT_MAX_PHONEMES);
+ }
+
+ /**
+ * Generates a new, fully-configured phonetic engine.
+ *
+ * @param nameType
+ * the type of names it will use
+ * @param ruleType
+ * the type of rules it will apply
+ * @param concat
+ * if it will concatenate multiple encodings
+ * @param maxPhonemes
+ * the maximum number of phonemes that will be handled
+ * @since 1.7
+ */
+ public PhoneticEngine(final NameType nameType, final RuleType ruleType, final boolean concat,
+ final int maxPhonemes) {
+ if (ruleType == RuleType.RULES) {
+ throw new IllegalArgumentException("ruleType must not be " + RuleType.RULES);
+ }
+ this.nameType = nameType;
+ this.ruleType = ruleType;
+ this.concat = concat;
+ this.lang = Lang.instance(nameType);
+ this.maxPhonemes = maxPhonemes;
+ }
+
+ /**
+ * Applies the final rules to convert from a language-specific phonetic representation to a
+ * language-independent representation.
+ *
+ * @param phonemeBuilder the current phonemes
+ * @param finalRules the final rules to apply
+ * @return the resulting phonemes
+ */
+ private PhonemeBuilder applyFinalRules(final PhonemeBuilder phonemeBuilder,
+ final Map> finalRules) {
+ if (finalRules == null) {
+ throw new NullPointerException("finalRules can not be null");
+ }
+ if (finalRules.isEmpty()) {
+ return phonemeBuilder;
+ }
+
+ final Map phonemes =
+ new TreeMap(Rule.Phoneme.COMPARATOR);
+
+ for (final Rule.Phoneme phoneme : phonemeBuilder.getPhonemes()) {
+ PhonemeBuilder subBuilder = PhonemeBuilder.empty(phoneme.getLanguages());
+ final String phonemeText = phoneme.getPhonemeText().toString();
+
+ for (int i = 0; i < phonemeText.length();) {
+ final RulesApplication rulesApplication =
+ new RulesApplication(finalRules, phonemeText, subBuilder, i, maxPhonemes).invoke();
+ final boolean found = rulesApplication.isFound();
+ subBuilder = rulesApplication.getPhonemeBuilder();
+
+ if (!found) {
+ // not found, appending as-is
+ subBuilder.append(phonemeText.subSequence(i, i + 1));
+ }
+
+ i = rulesApplication.getI();
+ }
+
+ // the phonemes map orders the phonemes only based on their text, but ignores the language set
+ // when adding new phonemes, check for equal phonemes and merge their language set, otherwise
+ // phonemes with the same text but different language set get lost
+ for (final Rule.Phoneme newPhoneme : subBuilder.getPhonemes()) {
+ if (phonemes.containsKey(newPhoneme)) {
+ final Rule.Phoneme oldPhoneme = phonemes.remove(newPhoneme);
+ final Rule.Phoneme mergedPhoneme = oldPhoneme.mergeWithLanguage(newPhoneme.getLanguages());
+ phonemes.put(mergedPhoneme, mergedPhoneme);
+ } else {
+ phonemes.put(newPhoneme, newPhoneme);
+ }
+ }
+ }
+
+ return new PhonemeBuilder(phonemes.keySet());
+ }
+
+ /**
+ * Encodes a string to its phonetic representation.
+ *
+ * @param input
+ * the String to encode
+ * @return the encoding of the input
+ */
+ public String encode(final String input) {
+ final Languages.LanguageSet languageSet = this.lang.guessLanguages(input);
+ return encode(input, languageSet);
+ }
+
+ /**
+ * Encodes an input string into an output phonetic representation, given a set of possible origin languages.
+ *
+ * @param input
+ * String to phoneticise; a String with dashes or spaces separating each word
+ * @param languageSet
+ * set of possible origin languages
+ * @return a phonetic representation of the input; a String containing '-'-separated phonetic representations of the
+ * input
+ */
+ public String encode(String input, final Languages.LanguageSet languageSet) {
+ final Map> rules = Rule.getInstanceMap(this.nameType, RuleType.RULES, languageSet);
+ // rules common across many (all) languages
+ final Map> finalRules1 = Rule.getInstanceMap(this.nameType, this.ruleType, "common");
+ // rules that apply to a specific language that may be ambiguous or wrong if applied to other languages
+ final Map> finalRules2 = Rule.getInstanceMap(this.nameType, this.ruleType, languageSet);
+
+ // tidy the input
+ // lower case is a locale-dependent operation
+ input = input.toLowerCase(Locale.ENGLISH).replace('-', ' ').trim();
+
+ if (this.nameType == NameType.GENERIC) {
+ if (input.length() >= 2 && input.substring(0, 2).equals("d'")) { // check for d'
+ final String remainder = input.substring(2);
+ final String combined = "d" + remainder;
+ return "(" + encode(remainder) + ")-(" + encode(combined) + ")";
+ }
+ for (final String l : NAME_PREFIXES.get(this.nameType)) {
+ // handle generic prefixes
+ if (input.startsWith(l + " ")) {
+ // check for any prefix in the words list
+ final String remainder = input.substring(l.length() + 1); // input without the prefix
+ final String combined = l + remainder; // input with prefix without space
+ return "(" + encode(remainder) + ")-(" + encode(combined) + ")";
+ }
+ }
+ }
+
+ final List words = Arrays.asList(input.split("\\s+"));
+ final List words2 = new ArrayList();
+
+ // special-case handling of word prefixes based upon the name type
+ switch (this.nameType) {
+ case SEPHARDIC:
+ for (final String aWord : words) {
+ final String[] parts = aWord.split("'");
+ final String lastPart = parts[parts.length - 1];
+ words2.add(lastPart);
+ }
+ words2.removeAll(NAME_PREFIXES.get(this.nameType));
+ break;
+ case ASHKENAZI:
+ words2.addAll(words);
+ words2.removeAll(NAME_PREFIXES.get(this.nameType));
+ break;
+ case GENERIC:
+ words2.addAll(words);
+ break;
+ default:
+ throw new IllegalStateException("Unreachable case: " + this.nameType);
+ }
+
+ if (this.concat) {
+ // concat mode enabled
+ input = join(words2, " ");
+ } else if (words2.size() == 1) {
+ // not a multi-word name
+ input = words.iterator().next();
+ } else {
+ // encode each word in a multi-word name separately (normally used for approx matches)
+ final StringBuilder result = new StringBuilder();
+ for (final String word : words2) {
+ result.append("-").append(encode(word));
+ }
+ // return the result without the leading "-"
+ return result.substring(1);
+ }
+
+ PhonemeBuilder phonemeBuilder = PhonemeBuilder.empty(languageSet);
+
+ // loop over each char in the input - we will handle the increment manually
+ for (int i = 0; i < input.length();) {
+ final RulesApplication rulesApplication =
+ new RulesApplication(rules, input, phonemeBuilder, i, maxPhonemes).invoke();
+ i = rulesApplication.getI();
+ phonemeBuilder = rulesApplication.getPhonemeBuilder();
+ }
+
+ // Apply the general rules
+ phonemeBuilder = applyFinalRules(phonemeBuilder, finalRules1);
+ // Apply the language-specific rules
+ phonemeBuilder = applyFinalRules(phonemeBuilder, finalRules2);
+
+ return phonemeBuilder.makeString();
+ }
+
+ /**
+ * Gets the Lang language guessing rules being used.
+ *
+ * @return the Lang in use
+ */
+ public Lang getLang() {
+ return this.lang;
+ }
+
+ /**
+ * Gets the NameType being used.
+ *
+ * @return the NameType in use
+ */
+ public NameType getNameType() {
+ return this.nameType;
+ }
+
+ /**
+ * Gets the RuleType being used.
+ *
+ * @return the RuleType in use
+ */
+ public RuleType getRuleType() {
+ return this.ruleType;
+ }
+
+ /**
+ * Gets if multiple phonetic encodings are concatenated or if just the first one is kept.
+ *
+ * @return true if multiple phonetic encodings are returned, false if just the first is
+ */
+ public boolean isConcat() {
+ return this.concat;
+ }
+
+ /**
+ * Gets the maximum number of phonemes the engine will calculate for a given input.
+ *
+ * @return the maximum number of phonemes
+ * @since 1.7
+ */
+ public int getMaxPhonemes() {
+ return this.maxPhonemes;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/ResourceConstants.java b/src/main/java/org/apache/commons/codec/language/bm/ResourceConstants.java
new file mode 100644
index 00000000..6558f0da
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/ResourceConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import org.apache.commons.codec.CharEncoding;
+
+/**
+ * Constants used to process resource files.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+class ResourceConstants {
+
+ static final String CMT = "//";
+ static final String ENCODING = CharEncoding.UTF_8;
+ static final String EXT_CMT_END = "*/";
+ static final String EXT_CMT_START = "/*";
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/Rule.java b/src/main/java/org/apache/commons/codec/language/bm/Rule.java
new file mode 100644
index 00000000..eacbae85
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/Rule.java
@@ -0,0 +1,720 @@
+/*
+ * 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.commons.codec.language.bm;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.language.bm.Languages.LanguageSet;
+
+/**
+ * A phoneme rule.
+ *
+ * Rules have a pattern, left context, right context, output phoneme, set of languages for which they apply
+ * and a logical flag indicating if all languages must be in play. A rule matches if:
+ *
+ * the pattern matches at the current position
+ * the string up until the beginning of the pattern matches the left context
+ * the string from the end of the pattern matches the right context
+ * logical is ALL and all languages are in scope; or
+ * logical is any other value and at least one language is in scope
+ *
+ *
+ * Rules are typically generated by parsing rules resources. In normal use, there will be no need for the user
+ * to explicitly construct their own.
+ *
+ * Rules are immutable and thread-safe.
+ *
+ * Rules resources
+ *
+ * Rules are typically loaded from resource files. These are UTF-8 encoded text files. They are systematically
+ * named following the pattern:
+ *
org/apache/commons/codec/language/bm/${NameType#getName}_${RuleType#getName}_${language}.txt
+ *
+ * The format of these resources is the following:
+ *
+ * Rules: whitespace separated, double-quoted strings. There should be 4 columns to each row, and these
+ * will be interpreted as:
+ *
+ * pattern
+ * left context
+ * right context
+ * phoneme
+ *
+ *
+ * End-of-line comments: Any occurrence of '//' will cause all text following on that line to be discarded
+ * as a comment.
+ * Multi-line comments: Any line starting with '/*' will start multi-line commenting mode. This will skip
+ * all content until a line ending in '*' and '/' is found.
+ * Blank lines: All blank lines will be skipped.
+ *
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public class Rule {
+
+ public static final class Phoneme implements PhonemeExpr {
+ public static final Comparator COMPARATOR = new Comparator() {
+ @Override
+ public int compare(final Phoneme o1, final Phoneme o2) {
+ for (int i = 0; i < o1.phonemeText.length(); i++) {
+ if (i >= o2.phonemeText.length()) {
+ return +1;
+ }
+ final int c = o1.phonemeText.charAt(i) - o2.phonemeText.charAt(i);
+ if (c != 0) {
+ return c;
+ }
+ }
+
+ if (o1.phonemeText.length() < o2.phonemeText.length()) {
+ return -1;
+ }
+
+ return 0;
+ }
+ };
+
+ private final StringBuilder phonemeText;
+ private final Languages.LanguageSet languages;
+
+ public Phoneme(final CharSequence phonemeText, final Languages.LanguageSet languages) {
+ this.phonemeText = new StringBuilder(phonemeText);
+ this.languages = languages;
+ }
+
+ public Phoneme(final Phoneme phonemeLeft, final Phoneme phonemeRight) {
+ this(phonemeLeft.phonemeText, phonemeLeft.languages);
+ this.phonemeText.append(phonemeRight.phonemeText);
+ }
+
+ public Phoneme(final Phoneme phonemeLeft, final Phoneme phonemeRight, final Languages.LanguageSet languages) {
+ this(phonemeLeft.phonemeText, languages);
+ this.phonemeText.append(phonemeRight.phonemeText);
+ }
+
+ public Phoneme append(final CharSequence str) {
+ this.phonemeText.append(str);
+ return this;
+ }
+
+ public Languages.LanguageSet getLanguages() {
+ return this.languages;
+ }
+
+ @Override
+ public Iterable getPhonemes() {
+ return Collections.singleton(this);
+ }
+
+ public CharSequence getPhonemeText() {
+ return this.phonemeText;
+ }
+
+ /**
+ * Deprecated since 1.9.
+ *
+ * @param right the Phoneme to join
+ * @return a new Phoneme
+ * @deprecated since 1.9
+ */
+ @Deprecated
+ public Phoneme join(final Phoneme right) {
+ return new Phoneme(this.phonemeText.toString() + right.phonemeText.toString(),
+ this.languages.restrictTo(right.languages));
+ }
+
+ /**
+ * Returns a new Phoneme with the same text but a union of its
+ * current language set and the given one.
+ *
+ * @param lang the language set to merge
+ * @return a new Phoneme
+ */
+ public Phoneme mergeWithLanguage(final LanguageSet lang) {
+ return new Phoneme(this.phonemeText.toString(), this.languages.merge(lang));
+ }
+
+ @Override
+ public String toString() {
+ return phonemeText.toString() + "[" + languages + "]";
+ }
+ }
+
+ public interface PhonemeExpr {
+ Iterable getPhonemes();
+ }
+
+ public static final class PhonemeList implements PhonemeExpr {
+ private final List phonemes;
+
+ public PhonemeList(final List phonemes) {
+ this.phonemes = phonemes;
+ }
+
+ @Override
+ public List getPhonemes() {
+ return this.phonemes;
+ }
+ }
+
+ /**
+ * A minimal wrapper around the functionality of Pattern that we use, to allow for alternate implementations.
+ */
+ public interface RPattern {
+ boolean isMatch(CharSequence input);
+ }
+
+ public static final RPattern ALL_STRINGS_RMATCHER = new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return true;
+ }
+ };
+
+ public static final String ALL = "ALL";
+
+ private static final String DOUBLE_QUOTE = "\"";
+
+ private static final String HASH_INCLUDE = "#include";
+
+ private static final Map>>>> RULES =
+ new EnumMap>>>>(NameType.class);
+
+ static {
+ for (final NameType s : NameType.values()) {
+ final Map>>> rts =
+ new EnumMap>>>(RuleType.class);
+
+ for (final RuleType rt : RuleType.values()) {
+ final Map>> rs = new HashMap>>();
+
+ final Languages ls = Languages.getInstance(s);
+ for (final String l : ls.getLanguages()) {
+ try {
+ rs.put(l, parseRules(createScanner(s, rt, l), createResourceName(s, rt, l)));
+ } catch (final IllegalStateException e) {
+ throw new IllegalStateException("Problem processing " + createResourceName(s, rt, l), e);
+ }
+ }
+ if (!rt.equals(RuleType.RULES)) {
+ rs.put("common", parseRules(createScanner(s, rt, "common"), createResourceName(s, rt, "common")));
+ }
+
+ rts.put(rt, Collections.unmodifiableMap(rs));
+ }
+
+ RULES.put(s, Collections.unmodifiableMap(rts));
+ }
+ }
+
+ private static boolean contains(final CharSequence chars, final char input) {
+ for (int i = 0; i < chars.length(); i++) {
+ if (chars.charAt(i) == input) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String createResourceName(final NameType nameType, final RuleType rt, final String lang) {
+ return String.format("org/apache/commons/codec/language/bm/%s_%s_%s.txt",
+ nameType.getName(), rt.getName(), lang);
+ }
+
+ private static Scanner createScanner(final NameType nameType, final RuleType rt, final String lang) {
+ final String resName = createResourceName(nameType, rt, lang);
+ final InputStream rulesIS = Languages.class.getClassLoader().getResourceAsStream(resName);
+
+ if (rulesIS == null) {
+ throw new IllegalArgumentException("Unable to load resource: " + resName);
+ }
+
+ return new Scanner(rulesIS, ResourceConstants.ENCODING);
+ }
+
+ private static Scanner createScanner(final String lang) {
+ final String resName = String.format("org/apache/commons/codec/language/bm/%s.txt", lang);
+ final InputStream rulesIS = Languages.class.getClassLoader().getResourceAsStream(resName);
+
+ if (rulesIS == null) {
+ throw new IllegalArgumentException("Unable to load resource: " + resName);
+ }
+
+ return new Scanner(rulesIS, ResourceConstants.ENCODING);
+ }
+
+ private static boolean endsWith(final CharSequence input, final CharSequence suffix) {
+ if (suffix.length() > input.length()) {
+ return false;
+ }
+ for (int i = input.length() - 1, j = suffix.length() - 1; j >= 0; i--, j--) {
+ if (input.charAt(i) != suffix.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets rules for a combination of name type, rule type and languages.
+ *
+ * @param nameType
+ * the NameType to consider
+ * @param rt
+ * the RuleType to consider
+ * @param langs
+ * the set of languages to consider
+ * @return a list of Rules that apply
+ */
+ public static List getInstance(final NameType nameType, final RuleType rt,
+ final Languages.LanguageSet langs) {
+ final Map> ruleMap = getInstanceMap(nameType, rt, langs);
+ final List allRules = new ArrayList();
+ for (final List rules : ruleMap.values()) {
+ allRules.addAll(rules);
+ }
+ return allRules;
+ }
+
+ /**
+ * Gets rules for a combination of name type, rule type and a single language.
+ *
+ * @param nameType
+ * the NameType to consider
+ * @param rt
+ * the RuleType to consider
+ * @param lang
+ * the language to consider
+ * @return a list of Rules that apply
+ */
+ public static List getInstance(final NameType nameType, final RuleType rt, final String lang) {
+ return getInstance(nameType, rt, LanguageSet.from(new HashSet(Arrays.asList(lang))));
+ }
+
+ /**
+ * Gets rules for a combination of name type, rule type and languages.
+ *
+ * @param nameType
+ * the NameType to consider
+ * @param rt
+ * the RuleType to consider
+ * @param langs
+ * the set of languages to consider
+ * @return a map containing all Rules that apply, grouped by the first character of the rule pattern
+ * @since 1.9
+ */
+ public static Map> getInstanceMap(final NameType nameType, final RuleType rt,
+ final Languages.LanguageSet langs) {
+ return langs.isSingleton() ? getInstanceMap(nameType, rt, langs.getAny()) :
+ getInstanceMap(nameType, rt, Languages.ANY);
+ }
+
+ /**
+ * Gets rules for a combination of name type, rule type and a single language.
+ *
+ * @param nameType
+ * the NameType to consider
+ * @param rt
+ * the RuleType to consider
+ * @param lang
+ * the language to consider
+ * @return a map containing all Rules that apply, grouped by the first character of the rule pattern
+ * @since 1.9
+ */
+ public static Map> getInstanceMap(final NameType nameType, final RuleType rt,
+ final String lang) {
+ final Map> rules = RULES.get(nameType).get(rt).get(lang);
+
+ if (rules == null) {
+ throw new IllegalArgumentException(String.format("No rules found for %s, %s, %s.",
+ nameType.getName(), rt.getName(), lang));
+ }
+
+ return rules;
+ }
+
+ private static Phoneme parsePhoneme(final String ph) {
+ final int open = ph.indexOf("[");
+ if (open >= 0) {
+ if (!ph.endsWith("]")) {
+ throw new IllegalArgumentException("Phoneme expression contains a '[' but does not end in ']'");
+ }
+ final String before = ph.substring(0, open);
+ final String in = ph.substring(open + 1, ph.length() - 1);
+ final Set langs = new HashSet(Arrays.asList(in.split("[+]")));
+
+ return new Phoneme(before, Languages.LanguageSet.from(langs));
+ } else {
+ return new Phoneme(ph, Languages.ANY_LANGUAGE);
+ }
+ }
+
+ private static PhonemeExpr parsePhonemeExpr(final String ph) {
+ if (ph.startsWith("(")) { // we have a bracketed list of options
+ if (!ph.endsWith(")")) {
+ throw new IllegalArgumentException("Phoneme starts with '(' so must end with ')'");
+ }
+
+ final List phs = new ArrayList();
+ final String body = ph.substring(1, ph.length() - 1);
+ for (final String part : body.split("[|]")) {
+ phs.add(parsePhoneme(part));
+ }
+ if (body.startsWith("|") || body.endsWith("|")) {
+ phs.add(new Phoneme("", Languages.ANY_LANGUAGE));
+ }
+
+ return new PhonemeList(phs);
+ } else {
+ return parsePhoneme(ph);
+ }
+ }
+
+ private static Map> parseRules(final Scanner scanner, final String location) {
+ final Map> lines = new HashMap>();
+ int currentLine = 0;
+
+ boolean inMultilineComment = false;
+ while (scanner.hasNextLine()) {
+ currentLine++;
+ final String rawLine = scanner.nextLine();
+ String line = rawLine;
+
+ if (inMultilineComment) {
+ if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
+ inMultilineComment = false;
+ }
+ } else {
+ if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
+ inMultilineComment = true;
+ } else {
+ // discard comments
+ final int cmtI = line.indexOf(ResourceConstants.CMT);
+ if (cmtI >= 0) {
+ line = line.substring(0, cmtI);
+ }
+
+ // trim leading-trailing whitespace
+ line = line.trim();
+
+ if (line.length() == 0) {
+ continue; // empty lines can be safely skipped
+ }
+
+ if (line.startsWith(HASH_INCLUDE)) {
+ // include statement
+ final String incl = line.substring(HASH_INCLUDE.length()).trim();
+ if (incl.contains(" ")) {
+ throw new IllegalArgumentException("Malformed import statement '" + rawLine + "' in " +
+ location);
+ } else {
+ lines.putAll(parseRules(createScanner(incl), location + "->" + incl));
+ }
+ } else {
+ // rule
+ final String[] parts = line.split("\\s+");
+ if (parts.length != 4) {
+ throw new IllegalArgumentException("Malformed rule statement split into " + parts.length +
+ " parts: " + rawLine + " in " + location);
+ } else {
+ try {
+ final String pat = stripQuotes(parts[0]);
+ final String lCon = stripQuotes(parts[1]);
+ final String rCon = stripQuotes(parts[2]);
+ final PhonemeExpr ph = parsePhonemeExpr(stripQuotes(parts[3]));
+ final int cLine = currentLine;
+ final Rule r = new Rule(pat, lCon, rCon, ph) {
+ private final int myLine = cLine;
+ private final String loc = location;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Rule");
+ sb.append("{line=").append(myLine);
+ sb.append(", loc='").append(loc).append('\'');
+ sb.append(", pat='").append(pat).append('\'');
+ sb.append(", lcon='").append(lCon).append('\'');
+ sb.append(", rcon='").append(rCon).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+ };
+ final String patternKey = r.pattern.substring(0,1);
+ List rules = lines.get(patternKey);
+ if (rules == null) {
+ rules = new ArrayList();
+ lines.put(patternKey, rules);
+ }
+ rules.add(r);
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalStateException("Problem parsing line '" + currentLine + "' in " +
+ location, e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return lines;
+ }
+
+ /**
+ * Attempts to compile the regex into direct string ops, falling back to Pattern and Matcher in the worst case.
+ *
+ * @param regex
+ * the regular expression to compile
+ * @return an RPattern that will match this regex
+ */
+ private static RPattern pattern(final String regex) {
+ final boolean startsWith = regex.startsWith("^");
+ final boolean endsWith = regex.endsWith("$");
+ final String content = regex.substring(startsWith ? 1 : 0, endsWith ? regex.length() - 1 : regex.length());
+ final boolean boxes = content.contains("[");
+
+ if (!boxes) {
+ if (startsWith && endsWith) {
+ // exact match
+ if (content.length() == 0) {
+ // empty
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return input.length() == 0;
+ }
+ };
+ } else {
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return input.equals(content);
+ }
+ };
+ }
+ } else if ((startsWith || endsWith) && content.length() == 0) {
+ // matches every string
+ return ALL_STRINGS_RMATCHER;
+ } else if (startsWith) {
+ // matches from start
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return startsWith(input, content);
+ }
+ };
+ } else if (endsWith) {
+ // matches from start
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return endsWith(input, content);
+ }
+ };
+ }
+ } else {
+ final boolean startsWithBox = content.startsWith("[");
+ final boolean endsWithBox = content.endsWith("]");
+
+ if (startsWithBox && endsWithBox) {
+ String boxContent = content.substring(1, content.length() - 1);
+ if (!boxContent.contains("[")) {
+ // box containing alternatives
+ final boolean negate = boxContent.startsWith("^");
+ if (negate) {
+ boxContent = boxContent.substring(1);
+ }
+ final String bContent = boxContent;
+ final boolean shouldMatch = !negate;
+
+ if (startsWith && endsWith) {
+ // exact match
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return input.length() == 1 && contains(bContent, input.charAt(0)) == shouldMatch;
+ }
+ };
+ } else if (startsWith) {
+ // first char
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return input.length() > 0 && contains(bContent, input.charAt(0)) == shouldMatch;
+ }
+ };
+ } else if (endsWith) {
+ // last char
+ return new RPattern() {
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ return input.length() > 0 &&
+ contains(bContent, input.charAt(input.length() - 1)) == shouldMatch;
+ }
+ };
+ }
+ }
+ }
+ }
+
+ return new RPattern() {
+ Pattern pattern = Pattern.compile(regex);
+
+ @Override
+ public boolean isMatch(final CharSequence input) {
+ final Matcher matcher = pattern.matcher(input);
+ return matcher.find();
+ }
+ };
+ }
+
+ private static boolean startsWith(final CharSequence input, final CharSequence prefix) {
+ if (prefix.length() > input.length()) {
+ return false;
+ }
+ for (int i = 0; i < prefix.length(); i++) {
+ if (input.charAt(i) != prefix.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String stripQuotes(String str) {
+ if (str.startsWith(DOUBLE_QUOTE)) {
+ str = str.substring(1);
+ }
+
+ if (str.endsWith(DOUBLE_QUOTE)) {
+ str = str.substring(0, str.length() - 1);
+ }
+
+ return str;
+ }
+
+ private final RPattern lContext;
+
+ private final String pattern;
+
+ private final PhonemeExpr phoneme;
+
+ private final RPattern rContext;
+
+ /**
+ * Creates a new rule.
+ *
+ * @param pattern
+ * the pattern
+ * @param lContext
+ * the left context
+ * @param rContext
+ * the right context
+ * @param phoneme
+ * the resulting phoneme
+ */
+ public Rule(final String pattern, final String lContext, final String rContext, final PhonemeExpr phoneme) {
+ this.pattern = pattern;
+ this.lContext = pattern(lContext + "$");
+ this.rContext = pattern("^" + rContext);
+ this.phoneme = phoneme;
+ }
+
+ /**
+ * Gets the left context. This is a regular expression that must match to the left of the pattern.
+ *
+ * @return the left context Pattern
+ */
+ public RPattern getLContext() {
+ return this.lContext;
+ }
+
+ /**
+ * Gets the pattern. This is a string-literal that must exactly match.
+ *
+ * @return the pattern
+ */
+ public String getPattern() {
+ return this.pattern;
+ }
+
+ /**
+ * Gets the phoneme. If the rule matches, this is the phoneme associated with the pattern match.
+ *
+ * @return the phoneme
+ */
+ public PhonemeExpr getPhoneme() {
+ return this.phoneme;
+ }
+
+ /**
+ * Gets the right context. This is a regular expression that must match to the right of the pattern.
+ *
+ * @return the right context Pattern
+ */
+ public RPattern getRContext() {
+ return this.rContext;
+ }
+
+ /**
+ * Decides if the pattern and context match the input starting at a position. It is a match if the
+ * lContext
matches input
up to i
, pattern
matches at i and
+ * rContext
matches from the end of the match of pattern
to the end of input
.
+ *
+ * @param input
+ * the input String
+ * @param i
+ * the int position within the input
+ * @return true if the pattern and left/right context match, false otherwise
+ */
+ public boolean patternAndContextMatches(final CharSequence input, final int i) {
+ if (i < 0) {
+ throw new IndexOutOfBoundsException("Can not match pattern at negative indexes");
+ }
+
+ final int patternLength = this.pattern.length();
+ final int ipl = i + patternLength;
+
+ if (ipl > input.length()) {
+ // not enough room for the pattern to match
+ return false;
+ }
+
+ // evaluate the pattern, left context and right context
+ // fail early if any of the evaluations is not successful
+ if (!input.subSequence(i, ipl).equals(this.pattern)) {
+ return false;
+ } else if (!this.rContext.isMatch(input.subSequence(ipl, input.length()))) {
+ return false;
+ }
+ return this.lContext.isMatch(input.subSequence(0, i));
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/RuleType.java b/src/main/java/org/apache/commons/codec/language/bm/RuleType.java
new file mode 100644
index 00000000..0e7608b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/RuleType.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.codec.language.bm;
+
+/**
+ * Types of rule.
+ *
+ * @since 1.6
+ * @version $Id$
+ */
+public enum RuleType {
+
+ /** Approximate rules, which will lead to the largest number of phonetic interpretations. */
+ APPROX("approx"),
+ /** Exact rules, which will lead to a minimum number of phonetic interpretations. */
+ EXACT("exact"),
+ /** For internal use only. Please use {@link #APPROX} or {@link #EXACT}. */
+ RULES("rules");
+
+ private final String name;
+
+ RuleType(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the rule name.
+ *
+ * @return the rule name.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/language/bm/package.html b/src/main/java/org/apache/commons/codec/language/bm/package.html
new file mode 100644
index 00000000..95a02ebb
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/bm/package.html
@@ -0,0 +1,21 @@
+
+
+
+ Implementation details of the Beider-Morse codec.
+
+
diff --git a/src/main/java/org/apache/commons/codec/language/package.html b/src/main/java/org/apache/commons/codec/language/package.html
new file mode 100644
index 00000000..6e337668
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/language/package.html
@@ -0,0 +1,21 @@
+
+
+
+ Language and phonetic encoders.
+
+
diff --git a/src/main/java/org/apache/commons/codec/net/BCodec.java b/src/main/java/org/apache/commons/codec/net/BCodec.java
new file mode 100644
index 00000000..651ed979
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/BCodec.java
@@ -0,0 +1,251 @@
+/*
+ * 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.commons.codec.net;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringDecoder;
+import org.apache.commons.codec.StringEncoder;
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * Identical to the Base64 encoding defined by RFC 1521
+ * and allows a character set to be specified.
+ *
+ * RFC 1522 describes techniques to allow the encoding of non-ASCII
+ * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
+ * handling software.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see MIME (Multipurpose Internet Mail Extensions) Part Two: Message
+ * Header Extensions for Non-ASCII Text
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
+ /**
+ * The default charset used for string decoding and encoding.
+ */
+ private final Charset charset;
+
+ /**
+ * Default constructor.
+ */
+ public BCodec() {
+ this(Charsets.UTF_8);
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset
+ *
+ * @param charset
+ * the default string charset to use.
+ *
+ * @see Standard charsets
+ * @since 1.7
+ */
+ public BCodec(final Charset charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset
+ *
+ * @param charsetName
+ * the default charset to use.
+ * @throws java.nio.charset.UnsupportedCharsetException
+ * If the named charset is unavailable
+ * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
+ * @see Standard charsets
+ */
+ public BCodec(final String charsetName) {
+ this(Charset.forName(charsetName));
+ }
+
+ @Override
+ protected String getEncoding() {
+ return "B";
+ }
+
+ @Override
+ protected byte[] doEncoding(final byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ return Base64.encodeBase64(bytes);
+ }
+
+ @Override
+ protected byte[] doDecoding(final byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ return Base64.decodeBase64(bytes);
+ }
+
+ /**
+ * Encodes a string into its Base64 form using the specified charset. Unsafe characters are escaped.
+ *
+ * @param value
+ * string to convert to Base64 form
+ * @param charset
+ * the charset for value
+ * @return Base64 string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ * @since 1.7
+ */
+ public String encode(final String value, final Charset charset) throws EncoderException {
+ if (value == null) {
+ return null;
+ }
+ return encodeText(value, charset);
+ }
+
+ /**
+ * Encodes a string into its Base64 form using the specified charset. Unsafe characters are escaped.
+ *
+ * @param value
+ * string to convert to Base64 form
+ * @param charset
+ * the charset for value
+ * @return Base64 string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ public String encode(final String value, final String charset) throws EncoderException {
+ if (value == null) {
+ return null;
+ }
+ try {
+ return this.encodeText(value, charset);
+ } catch (final UnsupportedEncodingException e) {
+ throw new EncoderException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes a string into its Base64 form using the default charset. Unsafe characters are escaped.
+ *
+ * @param value
+ * string to convert to Base64 form
+ * @return Base64 string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ @Override
+ public String encode(final String value) throws EncoderException {
+ if (value == null) {
+ return null;
+ }
+ return encode(value, this.getCharset());
+ }
+
+ /**
+ * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param value
+ * Base64 string to convert into its original form
+ * @return original string
+ * @throws DecoderException
+ * A decoder exception is thrown if a failure condition is encountered during the decode process.
+ */
+ @Override
+ public String decode(final String value) throws DecoderException {
+ if (value == null) {
+ return null;
+ }
+ try {
+ return this.decodeText(value);
+ } catch (final UnsupportedEncodingException e) {
+ throw new DecoderException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes an object into its Base64 form using the default charset. Unsafe characters are escaped.
+ *
+ * @param value
+ * object to convert to Base64 form
+ * @return Base64 object
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ @Override
+ public Object encode(final Object value) throws EncoderException {
+ if (value == null) {
+ return null;
+ } else if (value instanceof String) {
+ return encode((String) value);
+ } else {
+ throw new EncoderException("Objects of type " +
+ value.getClass().getName() +
+ " cannot be encoded using BCodec");
+ }
+ }
+
+ /**
+ * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param value
+ * Base64 object to convert into its original form
+ * @return original object
+ * @throws DecoderException
+ * Thrown if the argument is not a String
. Thrown if a failure condition is encountered
+ * during the decode process.
+ */
+ @Override
+ public Object decode(final Object value) throws DecoderException {
+ if (value == null) {
+ return null;
+ } else if (value instanceof String) {
+ return decode((String) value);
+ } else {
+ throw new DecoderException("Objects of type " +
+ value.getClass().getName() +
+ " cannot be decoded using BCodec");
+ }
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ * @since 1.7
+ */
+ public Charset getCharset() {
+ return this.charset;
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ */
+ public String getDefaultCharset() {
+ return this.charset.name();
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/net/QCodec.java b/src/main/java/org/apache/commons/codec/net/QCodec.java
new file mode 100644
index 00000000..584b3ff0
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/QCodec.java
@@ -0,0 +1,358 @@
+/*
+ * 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.commons.codec.net;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringDecoder;
+import org.apache.commons.codec.StringEncoder;
+
+/**
+ * Similar to the Quoted-Printable content-transfer-encoding defined in
+ * RFC 1521 and designed to allow text containing mostly ASCII
+ * characters to be decipherable on an ASCII terminal without decoding.
+ *
+ * RFC 1522 describes techniques to allow the encoding of non-ASCII
+ * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
+ * handling software.
+ *
+ * This class is conditionally thread-safe.
+ * The instance field {@link #encodeBlanks} is mutable {@link #setEncodeBlanks(boolean)}
+ * but is not volatile, and accesses are not synchronised.
+ * If an instance of the class is shared between threads, the caller needs to ensure that suitable synchronisation
+ * is used to ensure safe publication of the value between threads, and must not invoke
+ * {@link #setEncodeBlanks(boolean)} after initial setup.
+ *
+ * @see MIME (Multipurpose Internet Mail Extensions) Part Two: Message
+ * Header Extensions for Non-ASCII Text
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
+ /**
+ * The default charset used for string decoding and encoding.
+ */
+ private final Charset charset;
+
+ /**
+ * BitSet of printable characters as defined in RFC 1522.
+ */
+ private static final BitSet PRINTABLE_CHARS = new BitSet(256);
+ // Static initializer for printable chars collection
+ static {
+ // alpha characters
+ PRINTABLE_CHARS.set(' ');
+ PRINTABLE_CHARS.set('!');
+ PRINTABLE_CHARS.set('"');
+ PRINTABLE_CHARS.set('#');
+ PRINTABLE_CHARS.set('$');
+ PRINTABLE_CHARS.set('%');
+ PRINTABLE_CHARS.set('&');
+ PRINTABLE_CHARS.set('\'');
+ PRINTABLE_CHARS.set('(');
+ PRINTABLE_CHARS.set(')');
+ PRINTABLE_CHARS.set('*');
+ PRINTABLE_CHARS.set('+');
+ PRINTABLE_CHARS.set(',');
+ PRINTABLE_CHARS.set('-');
+ PRINTABLE_CHARS.set('.');
+ PRINTABLE_CHARS.set('/');
+ for (int i = '0'; i <= '9'; i++) {
+ PRINTABLE_CHARS.set(i);
+ }
+ PRINTABLE_CHARS.set(':');
+ PRINTABLE_CHARS.set(';');
+ PRINTABLE_CHARS.set('<');
+ PRINTABLE_CHARS.set('>');
+ PRINTABLE_CHARS.set('@');
+ for (int i = 'A'; i <= 'Z'; i++) {
+ PRINTABLE_CHARS.set(i);
+ }
+ PRINTABLE_CHARS.set('[');
+ PRINTABLE_CHARS.set('\\');
+ PRINTABLE_CHARS.set(']');
+ PRINTABLE_CHARS.set('^');
+ PRINTABLE_CHARS.set('`');
+ for (int i = 'a'; i <= 'z'; i++) {
+ PRINTABLE_CHARS.set(i);
+ }
+ PRINTABLE_CHARS.set('{');
+ PRINTABLE_CHARS.set('|');
+ PRINTABLE_CHARS.set('}');
+ PRINTABLE_CHARS.set('~');
+ }
+
+ private static final byte BLANK = 32;
+
+ private static final byte UNDERSCORE = 95;
+
+ private boolean encodeBlanks = false;
+
+ /**
+ * Default constructor.
+ */
+ public QCodec() {
+ this(Charsets.UTF_8);
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset.
+ *
+ * @param charset
+ * the default string charset to use.
+ *
+ * @see Standard charsets
+ * @since 1.7
+ */
+ public QCodec(final Charset charset) {
+ super();
+ this.charset = charset;
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset.
+ *
+ * @param charsetName
+ * the charset to use.
+ * @throws java.nio.charset.UnsupportedCharsetException
+ * If the named charset is unavailable
+ * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
+ * @see Standard charsets
+ */
+ public QCodec(final String charsetName) {
+ this(Charset.forName(charsetName));
+ }
+
+ @Override
+ protected String getEncoding() {
+ return "Q";
+ }
+
+ @Override
+ protected byte[] doEncoding(final byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ final byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes);
+ if (this.encodeBlanks) {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == BLANK) {
+ data[i] = UNDERSCORE;
+ }
+ }
+ }
+ return data;
+ }
+
+ @Override
+ protected byte[] doDecoding(final byte[] bytes) throws DecoderException {
+ if (bytes == null) {
+ return null;
+ }
+ boolean hasUnderscores = false;
+ for (final byte b : bytes) {
+ if (b == UNDERSCORE) {
+ hasUnderscores = true;
+ break;
+ }
+ }
+ if (hasUnderscores) {
+ final byte[] tmp = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ final byte b = bytes[i];
+ if (b != UNDERSCORE) {
+ tmp[i] = b;
+ } else {
+ tmp[i] = BLANK;
+ }
+ }
+ return QuotedPrintableCodec.decodeQuotedPrintable(tmp);
+ }
+ return QuotedPrintableCodec.decodeQuotedPrintable(bytes);
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @param charset
+ * the charset for str
+ * @return quoted-printable string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ * @since 1.7
+ */
+ public String encode(final String str, final Charset charset) throws EncoderException {
+ if (str == null) {
+ return null;
+ }
+ return encodeText(str, charset);
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @param charset
+ * the charset for str
+ * @return quoted-printable string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ public String encode(final String str, final String charset) throws EncoderException {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return encodeText(str, charset);
+ } catch (final UnsupportedEncodingException e) {
+ throw new EncoderException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the default charset. Unsafe characters are escaped.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @return quoted-printable string
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ @Override
+ public String encode(final String str) throws EncoderException {
+ if (str == null) {
+ return null;
+ }
+ return encode(str, getCharset());
+ }
+
+ /**
+ * Decodes a quoted-printable string into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param str
+ * quoted-printable string to convert into its original form
+ * @return original string
+ * @throws DecoderException
+ * A decoder exception is thrown if a failure condition is encountered during the decode process.
+ */
+ @Override
+ public String decode(final String str) throws DecoderException {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return decodeText(str);
+ } catch (final UnsupportedEncodingException e) {
+ throw new DecoderException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes an object into its quoted-printable form using the default charset. Unsafe characters are escaped.
+ *
+ * @param obj
+ * object to convert to quoted-printable form
+ * @return quoted-printable object
+ * @throws EncoderException
+ * thrown if a failure condition is encountered during the encoding process.
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof String) {
+ return encode((String) obj);
+ } else {
+ throw new EncoderException("Objects of type " +
+ obj.getClass().getName() +
+ " cannot be encoded using Q codec");
+ }
+ }
+
+ /**
+ * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param obj
+ * quoted-printable object to convert into its original form
+ * @return original object
+ * @throws DecoderException
+ * Thrown if the argument is not a String
. Thrown if a failure condition is encountered
+ * during the decode process.
+ */
+ @Override
+ public Object decode(final Object obj) throws DecoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof String) {
+ return decode((String) obj);
+ } else {
+ throw new DecoderException("Objects of type " +
+ obj.getClass().getName() +
+ " cannot be decoded using Q codec");
+ }
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ * @since 1.7
+ */
+ public Charset getCharset() {
+ return this.charset;
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ */
+ public String getDefaultCharset() {
+ return this.charset.name();
+ }
+
+ /**
+ * Tests if optional transformation of SPACE characters is to be used
+ *
+ * @return true
if SPACE characters are to be transformed, false
otherwise
+ */
+ public boolean isEncodeBlanks() {
+ return this.encodeBlanks;
+ }
+
+ /**
+ * Defines whether optional transformation of SPACE characters is to be used
+ *
+ * @param b
+ * true
if SPACE characters are to be transformed, false
otherwise
+ */
+ public void setEncodeBlanks(final boolean b) {
+ this.encodeBlanks = b;
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/net/QuotedPrintableCodec.java b/src/main/java/org/apache/commons/codec/net/QuotedPrintableCodec.java
new file mode 100644
index 00000000..82b88617
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/QuotedPrintableCodec.java
@@ -0,0 +1,602 @@
+/*
+ * 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.commons.codec.net;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.BitSet;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringDecoder;
+import org.apache.commons.codec.StringEncoder;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Codec for the Quoted-Printable section of RFC 1521 .
+ *
+ * The Quoted-Printable encoding is intended to represent data that largely consists of octets that correspond to
+ * printable characters in the ASCII character set. It encodes the data in such a way that the resulting octets are
+ * unlikely to be modified by mail transport. If the data being encoded are mostly ASCII text, the encoded form of the
+ * data remains largely recognizable by humans. A body which is entirely ASCII may also be encoded in Quoted-Printable
+ * to ensure the integrity of the data should the message pass through a character- translating, and/or line-wrapping
+ * gateway.
+ *
+ * Note:
+ *
+ * Depending on the selected {@code strict} parameter, this class will implement a different set of rules of the
+ * quoted-printable spec:
+ *
+ * {@code strict=false}: only rules #1 and #2 are implemented
+ * {@code strict=true}: all rules #1 through #5 are implemented
+ *
+ * Originally, this class only supported the non-strict mode, but the codec in this partial form could already be used
+ * for certain applications that do not require quoted-printable line formatting (rules #3, #4, #5), for instance
+ * Q codec. The strict mode has been added in 1.10.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One:
+ * Mechanisms for Specifying and Describing the Format of Internet Message Bodies
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+public class QuotedPrintableCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder {
+ /**
+ * The default charset used for string decoding and encoding.
+ */
+ private final Charset charset;
+
+ /**
+ * Indicates whether soft line breaks shall be used during encoding (rule #3-5).
+ */
+ private final boolean strict;
+
+ /**
+ * BitSet of printable characters as defined in RFC 1521.
+ */
+ private static final BitSet PRINTABLE_CHARS = new BitSet(256);
+
+ private static final byte ESCAPE_CHAR = '=';
+
+ private static final byte TAB = 9;
+
+ private static final byte SPACE = 32;
+
+ private static final byte CR = 13;
+
+ private static final byte LF = 10;
+
+ /**
+ * Safe line length for quoted printable encoded text.
+ */
+ private static final int SAFE_LENGTH = 73;
+
+ // Static initializer for printable chars collection
+ static {
+ // alpha characters
+ for (int i = 33; i <= 60; i++) {
+ PRINTABLE_CHARS.set(i);
+ }
+ for (int i = 62; i <= 126; i++) {
+ PRINTABLE_CHARS.set(i);
+ }
+ PRINTABLE_CHARS.set(TAB);
+ PRINTABLE_CHARS.set(SPACE);
+ }
+
+ /**
+ * Default constructor, assumes default charset of {@link Charsets#UTF_8}
+ */
+ public QuotedPrintableCodec() {
+ this(Charsets.UTF_8, false);
+ }
+
+ /**
+ * Constructor which allows for the selection of the strict mode.
+ *
+ * @param strict
+ * if {@code true}, soft line breaks will be used
+ * @since 1.10
+ */
+ public QuotedPrintableCodec(final boolean strict) {
+ this(Charsets.UTF_8, strict);
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset.
+ *
+ * @param charset
+ * the default string charset to use.
+ * @since 1.7
+ */
+ public QuotedPrintableCodec(final Charset charset) {
+ this(charset, false);
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset and strict mode.
+ *
+ * @param charset
+ * the default string charset to use.
+ * @param strict
+ * if {@code true}, soft line breaks will be used
+ * @since 1.10
+ */
+ public QuotedPrintableCodec(final Charset charset, final boolean strict) {
+ this.charset = charset;
+ this.strict = strict;
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset.
+ *
+ * @param charsetName
+ * the default string charset to use.
+ * @throws UnsupportedCharsetException
+ * If no support for the named charset is available
+ * in this instance of the Java virtual machine
+ * @throws IllegalArgumentException
+ * If the given charsetName is null
+ * @throws IllegalCharsetNameException
+ * If the given charset name is illegal
+ *
+ * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
+ */
+ public QuotedPrintableCodec(final String charsetName)
+ throws IllegalCharsetNameException, IllegalArgumentException, UnsupportedCharsetException {
+ this(Charset.forName(charsetName), false);
+ }
+
+ /**
+ * Encodes byte into its quoted-printable representation.
+ *
+ * @param b
+ * byte to encode
+ * @param buffer
+ * the buffer to write to
+ * @return The number of bytes written to the buffer
+ */
+ private static final int encodeQuotedPrintable(final int b, final ByteArrayOutputStream buffer) {
+ buffer.write(ESCAPE_CHAR);
+ final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+ final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+ buffer.write(hex1);
+ buffer.write(hex2);
+ return 3;
+ }
+
+ /**
+ * Return the byte at position index
of the byte array and
+ * make sure it is unsigned.
+ *
+ * @param index
+ * position in the array
+ * @param bytes
+ * the byte array
+ * @return the unsigned octet at position index
from the array
+ */
+ private static int getUnsignedOctet(final int index, final byte[] bytes) {
+ int b = bytes[index];
+ if (b < 0) {
+ b = 256 + b;
+ }
+ return b;
+ }
+
+ /**
+ * Write a byte to the buffer.
+ *
+ * @param b
+ * byte to write
+ * @param encode
+ * indicates whether the octet shall be encoded
+ * @param buffer
+ * the buffer to write to
+ * @return the number of bytes that have been written to the buffer
+ */
+ private static int encodeByte(final int b, final boolean encode,
+ final ByteArrayOutputStream buffer) {
+ if (encode) {
+ return encodeQuotedPrintable(b, buffer);
+ } else {
+ buffer.write(b);
+ return 1;
+ }
+ }
+
+ /**
+ * Checks whether the given byte is whitespace.
+ *
+ * @param b
+ * byte to be checked
+ * @return true
if the byte is either a space or tab character
+ */
+ private static boolean isWhitespace(final int b) {
+ return b == SPACE || b == TAB;
+ }
+
+ /**
+ * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
+ *
+ * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param printable
+ * bitset of characters deemed quoted-printable
+ * @param bytes
+ * array of bytes to be encoded
+ * @return array of bytes containing quoted-printable data
+ */
+ public static final byte[] encodeQuotedPrintable(BitSet printable, final byte[] bytes) {
+ return encodeQuotedPrintable(printable, bytes, false);
+ }
+
+ /**
+ * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
+ *
+ * Depending on the selection of the {@code strict} parameter, this function either implements the full ruleset
+ * or only a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param printable
+ * bitset of characters deemed quoted-printable
+ * @param bytes
+ * array of bytes to be encoded
+ * @param strict
+ * if {@code true} the full ruleset is used, otherwise only rule #1 and rule #2
+ * @return array of bytes containing quoted-printable data
+ * @since 1.10
+ */
+ public static final byte[] encodeQuotedPrintable(BitSet printable, final byte[] bytes, boolean strict) {
+ if (bytes == null) {
+ return null;
+ }
+ if (printable == null) {
+ printable = PRINTABLE_CHARS;
+ }
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ if (strict) {
+ int pos = 1;
+ // encode up to buffer.length - 3, the last three octets will be treated
+ // separately for simplification of note #3
+ for (int i = 0; i < bytes.length - 3; i++) {
+ int b = getUnsignedOctet(i, bytes);
+ if (pos < SAFE_LENGTH) {
+ // up to this length it is safe to add any byte, encoded or not
+ pos += encodeByte(b, !printable.get(b), buffer);
+ } else {
+ // rule #3: whitespace at the end of a line *must* be encoded
+ encodeByte(b, !printable.get(b) || isWhitespace(b), buffer);
+
+ // rule #5: soft line break
+ buffer.write(ESCAPE_CHAR);
+ buffer.write(CR);
+ buffer.write(LF);
+ pos = 1;
+ }
+ }
+
+ // rule #3: whitespace at the end of a line *must* be encoded
+ // if we would do a soft break line after this octet, encode whitespace
+ int b = getUnsignedOctet(bytes.length - 3, bytes);
+ boolean encode = !printable.get(b) || (isWhitespace(b) && pos > SAFE_LENGTH - 5);
+ pos += encodeByte(b, encode, buffer);
+
+ // note #3: '=' *must not* be the ultimate or penultimate character
+ // simplification: if < 6 bytes left, do a soft line break as we may need
+ // exactly 6 bytes space for the last 2 bytes
+ if (pos > SAFE_LENGTH - 2) {
+ buffer.write(ESCAPE_CHAR);
+ buffer.write(CR);
+ buffer.write(LF);
+ }
+ for (int i = bytes.length - 2; i < bytes.length; i++) {
+ b = getUnsignedOctet(i, bytes);
+ // rule #3: trailing whitespace shall be encoded
+ encode = !printable.get(b) || (i > bytes.length - 2 && isWhitespace(b));
+ encodeByte(b, encode, buffer);
+ }
+ } else {
+ for (final byte c : bytes) {
+ int b = c;
+ if (b < 0) {
+ b = 256 + b;
+ }
+ if (printable.get(b)) {
+ buffer.write(b);
+ } else {
+ encodeQuotedPrintable(b, buffer);
+ }
+ }
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Decodes an array quoted-printable characters into an array of original bytes. Escaped characters are converted
+ * back to their original representation.
+ *
+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #5) as
+ * defined in RFC 1521.
+ *
+ * @param bytes
+ * array of quoted-printable characters
+ * @return array of original bytes
+ * @throws DecoderException
+ * Thrown if quoted-printable decoding is unsuccessful
+ */
+ public static final byte[] decodeQuotedPrintable(final byte[] bytes) throws DecoderException {
+ if (bytes == null) {
+ return null;
+ }
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ final int b = bytes[i];
+ if (b == ESCAPE_CHAR) {
+ try {
+ // if the next octet is a CR we have found a soft line break
+ if (bytes[++i] == CR) {
+ continue;
+ }
+ final int u = Utils.digit16(bytes[i]);
+ final int l = Utils.digit16(bytes[++i]);
+ buffer.write((char) ((u << 4) + l));
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ throw new DecoderException("Invalid quoted-printable encoding", e);
+ }
+ } else if (b != CR && b != LF) {
+ // every other octet is appended except for CR & LF
+ buffer.write(b);
+ }
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
+ *
+ * Depending on the selection of the {@code strict} parameter, this function either implements the full ruleset
+ * or only a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param bytes
+ * array of bytes to be encoded
+ * @return array of bytes containing quoted-printable data
+ */
+ @Override
+ public byte[] encode(final byte[] bytes) {
+ return encodeQuotedPrintable(PRINTABLE_CHARS, bytes, strict);
+ }
+
+ /**
+ * Decodes an array of quoted-printable characters into an array of original bytes. Escaped characters are converted
+ * back to their original representation.
+ *
+ * This function fully implements the quoted-printable encoding specification (rule #1 through rule #5) as
+ * defined in RFC 1521.
+ *
+ * @param bytes
+ * array of quoted-printable characters
+ * @return array of original bytes
+ * @throws DecoderException
+ * Thrown if quoted-printable decoding is unsuccessful
+ */
+ @Override
+ public byte[] decode(final byte[] bytes) throws DecoderException {
+ return decodeQuotedPrintable(bytes);
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the default string charset. Unsafe characters are escaped.
+ *
+ * Depending on the selection of the {@code strict} parameter, this function either implements the full ruleset
+ * or only a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @return quoted-printable string
+ * @throws EncoderException
+ * Thrown if quoted-printable encoding is unsuccessful
+ *
+ * @see #getCharset()
+ */
+ @Override
+ public String encode(final String str) throws EncoderException {
+ return this.encode(str, getCharset());
+ }
+
+ /**
+ * Decodes a quoted-printable string into its original form using the specified string charset. Escaped characters
+ * are converted back to their original representation.
+ *
+ * @param str
+ * quoted-printable string to convert into its original form
+ * @param charset
+ * the original string charset
+ * @return original string
+ * @throws DecoderException
+ * Thrown if quoted-printable decoding is unsuccessful
+ * @since 1.7
+ */
+ public String decode(final String str, final Charset charset) throws DecoderException {
+ if (str == null) {
+ return null;
+ }
+ return new String(this.decode(StringUtils.getBytesUsAscii(str)), charset);
+ }
+
+ /**
+ * Decodes a quoted-printable string into its original form using the specified string charset. Escaped characters
+ * are converted back to their original representation.
+ *
+ * @param str
+ * quoted-printable string to convert into its original form
+ * @param charset
+ * the original string charset
+ * @return original string
+ * @throws DecoderException
+ * Thrown if quoted-printable decoding is unsuccessful
+ * @throws UnsupportedEncodingException
+ * Thrown if charset is not supported
+ */
+ public String decode(final String str, final String charset) throws DecoderException, UnsupportedEncodingException {
+ if (str == null) {
+ return null;
+ }
+ return new String(decode(StringUtils.getBytesUsAscii(str)), charset);
+ }
+
+ /**
+ * Decodes a quoted-printable string into its original form using the default string charset. Escaped characters are
+ * converted back to their original representation.
+ *
+ * @param str
+ * quoted-printable string to convert into its original form
+ * @return original string
+ * @throws DecoderException
+ * Thrown if quoted-printable decoding is unsuccessful. Thrown if charset is not supported.
+ * @see #getCharset()
+ */
+ @Override
+ public String decode(final String str) throws DecoderException {
+ return this.decode(str, this.getCharset());
+ }
+
+ /**
+ * Encodes an object into its quoted-printable safe form. Unsafe characters are escaped.
+ *
+ * @param obj
+ * string to convert to a quoted-printable form
+ * @return quoted-printable object
+ * @throws EncoderException
+ * Thrown if quoted-printable encoding is not applicable to objects of this type or if encoding is
+ * unsuccessful
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof byte[]) {
+ return encode((byte[]) obj);
+ } else if (obj instanceof String) {
+ return encode((String) obj);
+ } else {
+ throw new EncoderException("Objects of type " +
+ obj.getClass().getName() +
+ " cannot be quoted-printable encoded");
+ }
+ }
+
+ /**
+ * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param obj
+ * quoted-printable object to convert into its original form
+ * @return original object
+ * @throws DecoderException
+ * Thrown if the argument is not a String
or byte[]
. Thrown if a failure
+ * condition is encountered during the decode process.
+ */
+ @Override
+ public Object decode(final Object obj) throws DecoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof byte[]) {
+ return decode((byte[]) obj);
+ } else if (obj instanceof String) {
+ return decode((String) obj);
+ } else {
+ throw new DecoderException("Objects of type " +
+ obj.getClass().getName() +
+ " cannot be quoted-printable decoded");
+ }
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ * @since 1.7
+ */
+ public Charset getCharset() {
+ return this.charset;
+ }
+
+ /**
+ * Gets the default charset name used for string decoding and encoding.
+ *
+ * @return the default charset name
+ */
+ public String getDefaultCharset() {
+ return this.charset.name();
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
+ *
+ * Depending on the selection of the {@code strict} parameter, this function either implements the full ruleset
+ * or only a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @param charset
+ * the charset for str
+ * @return quoted-printable string
+ * @since 1.7
+ */
+ public String encode(final String str, final Charset charset) {
+ if (str == null) {
+ return null;
+ }
+ return StringUtils.newStringUsAscii(this.encode(str.getBytes(charset)));
+ }
+
+ /**
+ * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
+ *
+ * Depending on the selection of the {@code strict} parameter, this function either implements the full ruleset
+ * or only a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
+ * RFC 1521 and is suitable for encoding binary data and unformatted text.
+ *
+ * @param str
+ * string to convert to quoted-printable form
+ * @param charset
+ * the charset for str
+ * @return quoted-printable string
+ * @throws UnsupportedEncodingException
+ * Thrown if the charset is not supported
+ */
+ public String encode(final String str, final String charset) throws UnsupportedEncodingException {
+ if (str == null) {
+ return null;
+ }
+ return StringUtils.newStringUsAscii(encode(str.getBytes(charset)));
+ }
+}
diff --git a/src/main/java/org/apache/commons/codec/net/RFC1522Codec.java b/src/main/java/org/apache/commons/codec/net/RFC1522Codec.java
new file mode 100644
index 00000000..6cad34f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/RFC1522Codec.java
@@ -0,0 +1,186 @@
+/*
+ * 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.commons.codec.net;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Implements methods common to all codecs defined in RFC 1522.
+ *
+ * RFC 1522 describes techniques to allow the
+ * encoding of non-ASCII text in various portions of a RFC 822 [2] message header, in a manner which
+ * is unlikely to confuse existing message handling software.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see MIME (Multipurpose Internet Mail Extensions) Part Two:
+ * Message Header Extensions for Non-ASCII Text
+ *
+ * @since 1.3
+ * @version $Id$
+ */
+abstract class RFC1522Codec {
+
+ /** Separator. */
+ protected static final char SEP = '?';
+
+ /** Prefix. */
+ protected static final String POSTFIX = "?=";
+
+ /** Postfix. */
+ protected static final String PREFIX = "=?";
+
+ /**
+ * Applies an RFC 1522 compliant encoding scheme to the given string of text with the given charset.
+ *
+ * This method constructs the "encoded-word" header common to all the RFC 1522 codecs and then invokes
+ * {@link #doEncoding(byte [])} method of a concrete class to perform the specific encoding.
+ *
+ * @param text
+ * a string to encode
+ * @param charset
+ * a charset to be used
+ * @return RFC 1522 compliant "encoded-word"
+ * @throws EncoderException
+ * thrown if there is an error condition during the Encoding process.
+ * @see Standard charsets
+ */
+ protected String encodeText(final String text, final Charset charset) throws EncoderException {
+ if (text == null) {
+ return null;
+ }
+ final StringBuilder buffer = new StringBuilder();
+ buffer.append(PREFIX);
+ buffer.append(charset);
+ buffer.append(SEP);
+ buffer.append(this.getEncoding());
+ buffer.append(SEP);
+ final byte [] rawData = this.doEncoding(text.getBytes(charset));
+ buffer.append(StringUtils.newStringUsAscii(rawData));
+ buffer.append(POSTFIX);
+ return buffer.toString();
+ }
+
+ /**
+ * Applies an RFC 1522 compliant encoding scheme to the given string of text with the given charset.
+ *
+ * This method constructs the "encoded-word" header common to all the RFC 1522 codecs and then invokes
+ * {@link #doEncoding(byte [])} method of a concrete class to perform the specific encoding.
+ *
+ * @param text
+ * a string to encode
+ * @param charsetName
+ * the charset to use
+ * @return RFC 1522 compliant "encoded-word"
+ * @throws EncoderException
+ * thrown if there is an error condition during the Encoding process.
+ * @throws UnsupportedEncodingException
+ * if charset is not available
+ *
+ * @see Standard charsets
+ */
+ protected String encodeText(final String text, final String charsetName)
+ throws EncoderException, UnsupportedEncodingException {
+ if (text == null) {
+ return null;
+ }
+ return this.encodeText(text, Charset.forName(charsetName));
+ }
+
+ /**
+ * Applies an RFC 1522 compliant decoding scheme to the given string of text.
+ *
+ * This method processes the "encoded-word" header common to all the RFC 1522 codecs and then invokes
+ * {@link #doEncoding(byte [])} method of a concrete class to perform the specific decoding.
+ *
+ * @param text
+ * a string to decode
+ * @return A new decoded String or null
if the input is null
.
+ * @throws DecoderException
+ * thrown if there is an error condition during the decoding process.
+ * @throws UnsupportedEncodingException
+ * thrown if charset specified in the "encoded-word" header is not supported
+ */
+ protected String decodeText(final String text)
+ throws DecoderException, UnsupportedEncodingException {
+ if (text == null) {
+ return null;
+ }
+ if (!text.startsWith(PREFIX) || !text.endsWith(POSTFIX)) {
+ throw new DecoderException("RFC 1522 violation: malformed encoded content");
+ }
+ final int terminator = text.length() - 2;
+ int from = 2;
+ int to = text.indexOf(SEP, from);
+ if (to == terminator) {
+ throw new DecoderException("RFC 1522 violation: charset token not found");
+ }
+ final String charset = text.substring(from, to);
+ if (charset.equals("")) {
+ throw new DecoderException("RFC 1522 violation: charset not specified");
+ }
+ from = to + 1;
+ to = text.indexOf(SEP, from);
+ if (to == terminator) {
+ throw new DecoderException("RFC 1522 violation: encoding token not found");
+ }
+ final String encoding = text.substring(from, to);
+ if (!getEncoding().equalsIgnoreCase(encoding)) {
+ throw new DecoderException("This codec cannot decode " + encoding + " encoded content");
+ }
+ from = to + 1;
+ to = text.indexOf(SEP, from);
+ byte[] data = StringUtils.getBytesUsAscii(text.substring(from, to));
+ data = doDecoding(data);
+ return new String(data, charset);
+ }
+
+ /**
+ * Returns the codec name (referred to as encoding in the RFC 1522).
+ *
+ * @return name of the codec
+ */
+ protected abstract String getEncoding();
+
+ /**
+ * Encodes an array of bytes using the defined encoding scheme.
+ *
+ * @param bytes
+ * Data to be encoded
+ * @return A byte array containing the encoded data
+ * @throws EncoderException
+ * thrown if the Encoder encounters a failure condition during the encoding process.
+ */
+ protected abstract byte[] doEncoding(byte[] bytes) throws EncoderException;
+
+ /**
+ * Decodes an array of bytes using the defined encoding scheme.
+ *
+ * @param bytes
+ * Data to be decoded
+ * @return a byte array that contains decoded data
+ * @throws DecoderException
+ * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
+ */
+ protected abstract byte[] doDecoding(byte[] bytes) throws DecoderException;
+}
diff --git a/src/main/java/org/apache/commons/codec/net/URLCodec.java b/src/main/java/org/apache/commons/codec/net/URLCodec.java
new file mode 100644
index 00000000..e53cd635
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/URLCodec.java
@@ -0,0 +1,368 @@
+/*
+ * 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.commons.codec.net;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.CharEncoding;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.StringDecoder;
+import org.apache.commons.codec.StringEncoder;
+import org.apache.commons.codec.binary.StringUtils;
+
+/**
+ * Implements the 'www-form-urlencoded' encoding scheme, also misleadingly known as URL encoding.
+ *
+ * This codec is meant to be a replacement for standard Java classes {@link java.net.URLEncoder} and
+ * {@link java.net.URLDecoder} on older Java platforms, as these classes in Java versions below
+ * 1.4 rely on the platform's default charset encoding.
+ *
+ * This class is immutable and thread-safe.
+ *
+ * @see Chapter 17.13.4 Form content types
+ * of the HTML 4.01 Specification
+ *
+ * @since 1.2
+ * @version $Id$
+ */
+public class URLCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder {
+
+ /**
+ * Radix used in encoding and decoding.
+ */
+ static final int RADIX = 16;
+
+ /**
+ * The default charset used for string decoding and encoding.
+ *
+ * @deprecated TODO: This field will be changed to a private final Charset in 2.0.
+ */
+ @Deprecated
+ protected String charset;
+
+ /**
+ * Release 1.5 made this field final.
+ */
+ protected static final byte ESCAPE_CHAR = '%';
+ /**
+ * BitSet of www-form-url safe characters.
+ */
+ protected static final BitSet WWW_FORM_URL = new BitSet(256);
+
+ // Static initializer for www_form_url
+ static {
+ // alpha characters
+ for (int i = 'a'; i <= 'z'; i++) {
+ WWW_FORM_URL.set(i);
+ }
+ for (int i = 'A'; i <= 'Z'; i++) {
+ WWW_FORM_URL.set(i);
+ }
+ // numeric characters
+ for (int i = '0'; i <= '9'; i++) {
+ WWW_FORM_URL.set(i);
+ }
+ // special chars
+ WWW_FORM_URL.set('-');
+ WWW_FORM_URL.set('_');
+ WWW_FORM_URL.set('.');
+ WWW_FORM_URL.set('*');
+ // blank to be replaced with +
+ WWW_FORM_URL.set(' ');
+ }
+
+
+ /**
+ * Default constructor.
+ */
+ public URLCodec() {
+ this(CharEncoding.UTF_8);
+ }
+
+ /**
+ * Constructor which allows for the selection of a default charset.
+ *
+ * @param charset the default string charset to use.
+ */
+ public URLCodec(final String charset) {
+ super();
+ this.charset = charset;
+ }
+
+ /**
+ * Encodes an array of bytes into an array of URL safe 7-bit characters. Unsafe characters are escaped.
+ *
+ * @param urlsafe
+ * bitset of characters deemed URL safe
+ * @param bytes
+ * array of bytes to convert to URL safe characters
+ * @return array of bytes containing URL safe characters
+ */
+ public static final byte[] encodeUrl(BitSet urlsafe, final byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ if (urlsafe == null) {
+ urlsafe = WWW_FORM_URL;
+ }
+
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (final byte c : bytes) {
+ int b = c;
+ if (b < 0) {
+ b = 256 + b;
+ }
+ if (urlsafe.get(b)) {
+ if (b == ' ') {
+ b = '+';
+ }
+ buffer.write(b);
+ } else {
+ buffer.write(ESCAPE_CHAR);
+ final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
+ final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
+ buffer.write(hex1);
+ buffer.write(hex2);
+ }
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Decodes an array of URL safe 7-bit characters into an array of original bytes. Escaped characters are converted
+ * back to their original representation.
+ *
+ * @param bytes
+ * array of URL safe characters
+ * @return array of original bytes
+ * @throws DecoderException
+ * Thrown if URL decoding is unsuccessful
+ */
+ public static final byte[] decodeUrl(final byte[] bytes) throws DecoderException {
+ if (bytes == null) {
+ return null;
+ }
+ final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ final int b = bytes[i];
+ if (b == '+') {
+ buffer.write(' ');
+ } else if (b == ESCAPE_CHAR) {
+ try {
+ final int u = Utils.digit16(bytes[++i]);
+ final int l = Utils.digit16(bytes[++i]);
+ buffer.write((char) ((u << 4) + l));
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ throw new DecoderException("Invalid URL encoding: ", e);
+ }
+ } else {
+ buffer.write(b);
+ }
+ }
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Encodes an array of bytes into an array of URL safe 7-bit characters. Unsafe characters are escaped.
+ *
+ * @param bytes
+ * array of bytes to convert to URL safe characters
+ * @return array of bytes containing URL safe characters
+ */
+ @Override
+ public byte[] encode(final byte[] bytes) {
+ return encodeUrl(WWW_FORM_URL, bytes);
+ }
+
+
+ /**
+ * Decodes an array of URL safe 7-bit characters into an array of original bytes. Escaped characters are converted
+ * back to their original representation.
+ *
+ * @param bytes
+ * array of URL safe characters
+ * @return array of original bytes
+ * @throws DecoderException
+ * Thrown if URL decoding is unsuccessful
+ */
+ @Override
+ public byte[] decode(final byte[] bytes) throws DecoderException {
+ return decodeUrl(bytes);
+ }
+
+ /**
+ * Encodes a string into its URL safe form using the specified string charset. Unsafe characters are escaped.
+ *
+ * @param str
+ * string to convert to a URL safe form
+ * @param charset
+ * the charset for str
+ * @return URL safe string
+ * @throws UnsupportedEncodingException
+ * Thrown if charset is not supported
+ */
+ public String encode(final String str, final String charset) throws UnsupportedEncodingException {
+ if (str == null) {
+ return null;
+ }
+ return StringUtils.newStringUsAscii(encode(str.getBytes(charset)));
+ }
+
+ /**
+ * Encodes a string into its URL safe form using the default string charset. Unsafe characters are escaped.
+ *
+ * @param str
+ * string to convert to a URL safe form
+ * @return URL safe string
+ * @throws EncoderException
+ * Thrown if URL encoding is unsuccessful
+ *
+ * @see #getDefaultCharset()
+ */
+ @Override
+ public String encode(final String str) throws EncoderException {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return encode(str, getDefaultCharset());
+ } catch (final UnsupportedEncodingException e) {
+ throw new EncoderException(e.getMessage(), e);
+ }
+ }
+
+
+ /**
+ * Decodes a URL safe string into its original form using the specified encoding. Escaped characters are converted
+ * back to their original representation.
+ *
+ * @param str
+ * URL safe string to convert into its original form
+ * @param charset
+ * the original string charset
+ * @return original string
+ * @throws DecoderException
+ * Thrown if URL decoding is unsuccessful
+ * @throws UnsupportedEncodingException
+ * Thrown if charset is not supported
+ */
+ public String decode(final String str, final String charset) throws DecoderException, UnsupportedEncodingException {
+ if (str == null) {
+ return null;
+ }
+ return new String(decode(StringUtils.getBytesUsAscii(str)), charset);
+ }
+
+ /**
+ * Decodes a URL safe string into its original form using the default string charset. Escaped characters are
+ * converted back to their original representation.
+ *
+ * @param str
+ * URL safe string to convert into its original form
+ * @return original string
+ * @throws DecoderException
+ * Thrown if URL decoding is unsuccessful
+ * @see #getDefaultCharset()
+ */
+ @Override
+ public String decode(final String str) throws DecoderException {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return decode(str, getDefaultCharset());
+ } catch (final UnsupportedEncodingException e) {
+ throw new DecoderException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes an object into its URL safe form. Unsafe characters are escaped.
+ *
+ * @param obj
+ * string to convert to a URL safe form
+ * @return URL safe object
+ * @throws EncoderException
+ * Thrown if URL encoding is not applicable to objects of this type or if encoding is unsuccessful
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof byte[]) {
+ return encode((byte[])obj);
+ } else if (obj instanceof String) {
+ return encode((String)obj);
+ } else {
+ throw new EncoderException("Objects of type " + obj.getClass().getName() + " cannot be URL encoded");
+
+ }
+ }
+
+ /**
+ * Decodes a URL safe object into its original form. Escaped characters are converted back to their original
+ * representation.
+ *
+ * @param obj
+ * URL safe object to convert into its original form
+ * @return original object
+ * @throws DecoderException
+ * Thrown if the argument is not a String
or byte[]
. Thrown if a failure
+ * condition is encountered during the decode process.
+ */
+ @Override
+ public Object decode(final Object obj) throws DecoderException {
+ if (obj == null) {
+ return null;
+ } else if (obj instanceof byte[]) {
+ return decode((byte[]) obj);
+ } else if (obj instanceof String) {
+ return decode((String) obj);
+ } else {
+ throw new DecoderException("Objects of type " + obj.getClass().getName() + " cannot be URL decoded");
+
+ }
+ }
+
+ /**
+ * The default charset used for string decoding and encoding.
+ *
+ * @return the default string charset.
+ */
+ public String getDefaultCharset() {
+ return this.charset;
+ }
+
+ /**
+ * The String
encoding used for decoding and encoding.
+ *
+ * @return Returns the encoding.
+ *
+ * @deprecated Use {@link #getDefaultCharset()}, will be removed in 2.0.
+ */
+ @Deprecated
+ public String getEncoding() {
+ return this.charset;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/net/Utils.java b/src/main/java/org/apache/commons/codec/net/Utils.java
new file mode 100644
index 00000000..c482c0ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/Utils.java
@@ -0,0 +1,50 @@
+/*
+ * 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.commons.codec.net;
+
+import org.apache.commons.codec.DecoderException;
+
+/**
+ * Utility methods for this package.
+ *
+ *
This class is immutable and thread-safe.
+ *
+ * @version $Id$
+ * @since 1.4
+ */
+class Utils {
+
+ /**
+ * Returns the numeric value of the character b
in radix 16.
+ *
+ * @param b
+ * The byte to be converted.
+ * @return The numeric value represented by the character in radix 16.
+ *
+ * @throws DecoderException
+ * Thrown when the byte is not valid per {@link Character#digit(char,int)}
+ */
+ static int digit16(final byte b) throws DecoderException {
+ final int i = Character.digit((char) b, URLCodec.RADIX);
+ if (i == -1) {
+ throw new DecoderException("Invalid URL encoding: not a valid digit (radix " + URLCodec.RADIX + "): " + b);
+ }
+ return i;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/codec/net/package.html b/src/main/java/org/apache/commons/codec/net/package.html
new file mode 100644
index 00000000..2b8ceab2
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/net/package.html
@@ -0,0 +1,23 @@
+
+
+
+
+ Network related encoding and decoding.
+
+
+
diff --git a/src/main/java/org/apache/commons/codec/overview.html b/src/main/java/org/apache/commons/codec/overview.html
new file mode 100644
index 00000000..76b0a621
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/overview.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+This document is the API specification for the Apache Commons Codec Library, version 1.3.
+
+
+This library requires a JRE version of 1.2.2 or greater.
+The hypertext links originating from this document point to Sun's version 1.3 API as the 1.2.2 API documentation
+is no longer on-line.
+
+
+
diff --git a/src/main/java/org/apache/commons/codec/package.html b/src/main/java/org/apache/commons/codec/package.html
new file mode 100644
index 00000000..fc1fac13
--- /dev/null
+++ b/src/main/java/org/apache/commons/codec/package.html
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+ Interfaces and classes used by
+ the various implementations in the sub-packages.
+
+ Definitive implementations of commonly used encoders and decoders.
+
+ Codec is currently comprised of a modest set of utilities and a
+ simple framework for String encoding and decoding in three categories:
+ Binary Encoders, Language Encoders, and Network Encoders.
+
+
+
+
+
+
+ Codec contains a number of commonly used language and phonetic
+ encoders
+
+
+
+
+ Codec contains network related encoders
+
+
+
+
diff --git a/src/main/java/org/imgscalr/AsyncScalr.java b/src/main/java/org/imgscalr/AsyncScalr.java
new file mode 100644
index 00000000..3be52752
--- /dev/null
+++ b/src/main/java/org/imgscalr/AsyncScalr.java
@@ -0,0 +1,594 @@
+/**
+ * Copyright 2011 The Buzz Media, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.imgscalr;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImagingOpException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.imgscalr.Scalr.Method;
+import org.imgscalr.Scalr.Mode;
+import org.imgscalr.Scalr.Rotation;
+
+/**
+ * Class used to provide the asynchronous versions of all the methods defined in
+ * {@link Scalr} for the purpose of efficiently handling large amounts of image
+ * operations via a select number of processing threads asynchronously.
+ *
+ * Given that image-scaling operations, especially when working with large
+ * images, can be very hardware-intensive (both CPU and memory), in large-scale
+ * deployments (e.g. a busy web application) it becomes increasingly important
+ * that the scale operations performed by imgscalr be manageable so as not to
+ * fire off too many simultaneous operations that the JVM's heap explodes and
+ * runs out of memory or pegs the CPU on the host machine, staving all other
+ * running processes.
+ *
+ * Up until now it was left to the caller to implement their own serialization
+ * or limiting logic to handle these use-cases. Given imgscalr's popularity in
+ * web applications it was determined that this requirement be common enough
+ * that it should be integrated directly into the imgscalr library for everyone
+ * to benefit from.
+ *
+ * Every method in this class wraps the matching methods in the {@link Scalr}
+ * class in new {@link Callable} instances that are submitted to an internal
+ * {@link ExecutorService} for execution at a later date. A {@link Future} is
+ * returned to the caller representing the task that is either currently
+ * performing the scale operation or will at a future date depending on where it
+ * is in the {@link ExecutorService}'s queue. {@link Future#get()} or
+ * {@link Future#get(long, TimeUnit)} can be used to block on the
+ * Future
, waiting for the scale operation to complete and return
+ * the resultant {@link BufferedImage} to the caller.
+ *
+ * This design provides the following features:
+ *
+ * Non-blocking, asynchronous scale operations that can continue execution
+ * while waiting on the scaled result.
+ * Serialize all scale requests down into a maximum number of
+ * simultaneous scale operations with no additional/complex logic. The
+ * number of simultaneous scale operations is caller-configurable (see
+ * {@link #THREAD_COUNT}) so as best to optimize the host system (e.g. 1 scale
+ * thread per core).
+ * No need to worry about overloading the host system with too many scale
+ * operations, they will simply queue up in this class and execute in-order.
+ * Synchronous/blocking behavior can still be achieved (if desired) by
+ * calling get()
or get(long, TimeUnit)
immediately on
+ * the returned {@link Future} from any of the methods below.
+ *
+ * Performance
+ * When tuning this class for optimal performance, benchmarking your particular
+ * hardware is the best approach. For some rough guidelines though, there are
+ * two resources you want to watch closely:
+ *
+ * JVM Heap Memory (Assume physical machine memory is always sufficiently
+ * large)
+ * # of CPU Cores
+ *
+ * You never want to allocate more scaling threads than you have CPU cores and
+ * on a sufficiently busy host where some of the cores may be busy running a
+ * database or a web server, you will want to allocate even less scaling
+ * threads.
+ *
+ * So as a maximum you would never want more scaling threads than CPU cores in
+ * any situation and less so on a busy server.
+ *
+ * If you allocate more threads than you have available CPU cores, your scaling
+ * operations will slow down as the CPU will spend a considerable amount of time
+ * context-switching between threads on the same core trying to finish all the
+ * tasks in parallel. You might still be tempted to do this because of the I/O
+ * delay some threads will encounter reading images off disk, but when you do
+ * your own benchmarking you'll likely find (as I did) that the actual disk I/O
+ * necessary to pull the image data off disk is a much smaller portion of the
+ * execution time than the actual scaling operations.
+ *
+ * If you are executing on a storage medium that is unexpectedly slow and I/O is
+ * a considerable portion of the scaling operation (e.g. S3 or EBS volumes),
+ * feel free to try using more threads than CPU cores to see if that helps; but
+ * in most normal cases, it will only slow down all other parallel scaling
+ * operations.
+ *
+ * As for memory, every time an image is scaled it is decoded into a
+ * {@link BufferedImage} and stored in the JVM Heap space (decoded image
+ * instances are always larger than the source images on-disk). For larger
+ * images, that can use up quite a bit of memory. You will need to benchmark
+ * your particular use-cases on your hardware to get an idea of where the sweet
+ * spot is for this; if you are operating within tight memory bounds, you may
+ * want to limit simultaneous scaling operations to 1 or 2 regardless of the
+ * number of cores just to avoid having too many {@link BufferedImage} instances
+ * in JVM Heap space at the same time.
+ *
+ * These are rough metrics and behaviors to give you an idea of how best to tune
+ * this class for your deployment, but nothing can replacement writing a small
+ * Java class that scales a handful of images in a number of different ways and
+ * testing that directly on your deployment hardware.
+ * Resource Overhead
+ * The {@link ExecutorService} utilized by this class won't be initialized until
+ * one of the operation methods are called, at which point the
+ * service
will be instantiated for the first time and operation
+ * queued up.
+ *
+ * More specifically, if you have no need for asynchronous image processing
+ * offered by this class, you don't need to worry about wasted resources or
+ * hanging/idle threads as they will never be created if you never use this
+ * class.
+ * Cleaning up Service Threads
+ * By default the {@link Thread}s created by the internal
+ * {@link ThreadPoolExecutor} do not run in daemon
mode; which
+ * means they will block the host VM from exiting until they are explicitly shut
+ * down in a client application; in a server application the container will shut
+ * down the pool forcibly.
+ *
+ * If you have used the {@link AsyncScalr} class and are trying to shut down a
+ * client application, you will need to call {@link #getService()} then
+ * {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()}
+ * to have the threads terminated; you may also want to look at the
+ * {@link ExecutorService#awaitTermination(long, TimeUnit)} method if you'd like
+ * to more closely monitor the shutting down process (and finalization of
+ * pending scale operations).
+ * Reusing Shutdown AsyncScalr
+ * If you have previously called shutdown
on the underlying service
+ * utilized by this class, subsequent calls to any of the operations this class
+ * provides will invoke the internal {@link #checkService()} method which will
+ * replace the terminated underlying {@link ExecutorService} with a new one via
+ * the {@link #createService()} method.
+ * Custom Implementations
+ * If a subclass wants to customize the {@link ExecutorService} or
+ * {@link ThreadFactory} used under the covers, this can be done by overriding
+ * the {@link #createService()} method which is invoked by this class anytime a
+ * new {@link ExecutorService} is needed.
+ *
+ * By default the {@link #createService()} method delegates to the
+ * {@link #createService(ThreadFactory)} method with a new instance of
+ * {@link DefaultThreadFactory}. Either of these methods can be overridden and
+ * customized easily if desired.
+ *
+ * TIP : A common customization to this class is to make the
+ * {@link Thread}s generated by the underlying factory more server-friendly, in
+ * which case the caller would want to use an instance of the
+ * {@link ServerThreadFactory} when creating the new {@link ExecutorService}.
+ *
+ * This can be done in one line by overriding {@link #createService()} and
+ * returning the result of:
+ * return createService(new ServerThreadFactory());
+ *
+ * By default this class uses an {@link ThreadPoolExecutor} internally to handle
+ * execution of queued image operations. If a different type of
+ * {@link ExecutorService} is desired, again, simply overriding the
+ * {@link #createService()} method of choice is the right way to do that.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 3.2
+ */
+@SuppressWarnings("javadoc")
+public class AsyncScalr {
+ /**
+ * System property name used to set the number of threads the default
+ * underlying {@link ExecutorService} will use to process async image
+ * operations.
+ *
+ * Value is "imgscalr.async.threadCount
".
+ */
+ public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount";
+
+ /**
+ * Number of threads the internal {@link ExecutorService} will use to
+ * simultaneously execute scale requests.
+ *
+ * This value can be changed by setting the
+ * imgscalr.async.threadCount
system property (see
+ * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value > 0.
+ *
+ * Default value is 2
.
+ */
+ public static final int THREAD_COUNT = Integer.getInteger(
+ THREAD_COUNT_PROPERTY_NAME, 2);
+
+ /**
+ * Initializer used to verify the THREAD_COUNT system property.
+ */
+ static {
+ if (THREAD_COUNT < 1)
+ throw new RuntimeException("System property '"
+ + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to "
+ + THREAD_COUNT + ", but THREAD_COUNT must be > 0.");
+ }
+
+ protected static ExecutorService service;
+
+ /**
+ * Used to get access to the internal {@link ExecutorService} used by this
+ * class to process scale operations.
+ *
+ * NOTE : You will need to explicitly shutdown any service
+ * currently set on this class before the host JVM exits.
+ *
+ * You can call {@link ExecutorService#shutdown()} to wait for all scaling
+ * operations to complete first or call
+ * {@link ExecutorService#shutdownNow()} to kill any in-process operations
+ * and purge all pending operations before exiting.
+ *
+ * Additionally you can use
+ * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a
+ * shutdown command to try and wait until the service has finished all
+ * tasks.
+ *
+ * @return the current {@link ExecutorService} used by this class to process
+ * scale operations.
+ */
+ public static ExecutorService getService() {
+ return service;
+ }
+
+ /**
+ * @see Scalr#apply(BufferedImage, BufferedImageOp...)
+ */
+ public static Future apply(final BufferedImage src,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.apply(src, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...)
+ */
+ public static Future crop(final BufferedImage src,
+ final int width, final int height, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.crop(src, width, height, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...)
+ */
+ public static Future crop(final BufferedImage src,
+ final int x, final int y, final int width, final int height,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.crop(src, x, y, width, height, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#pad(BufferedImage, int, BufferedImageOp...)
+ */
+ public static Future pad(final BufferedImage src,
+ final int padding, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.pad(src, padding, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...)
+ */
+ public static Future pad(final BufferedImage src,
+ final int padding, final Color color, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.pad(src, padding, color, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final int targetSize, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, targetSize, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Method scalingMethod, final int targetSize,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, scalingMethod, targetSize, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Mode resizeMode, final int targetSize,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, resizeMode, targetSize, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Method scalingMethod, final Mode resizeMode,
+ final int targetSize, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, scalingMethod, resizeMode, targetSize,
+ ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final int targetWidth, final int targetHeight,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, targetWidth, targetHeight, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Method scalingMethod, final int targetWidth,
+ final int targetHeight, final BufferedImageOp... ops) {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, scalingMethod, targetWidth,
+ targetHeight, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Mode resizeMode, final int targetWidth,
+ final int targetHeight, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, resizeMode, targetWidth, targetHeight,
+ ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#resize(BufferedImage, Method, Mode, int, int,
+ * BufferedImageOp...)
+ */
+ public static Future resize(final BufferedImage src,
+ final Method scalingMethod, final Mode resizeMode,
+ final int targetWidth, final int targetHeight,
+ final BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.resize(src, scalingMethod, resizeMode,
+ targetWidth, targetHeight, ops);
+ }
+ });
+ }
+
+ /**
+ * @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...)
+ */
+ public static Future rotate(final BufferedImage src,
+ final Rotation rotation, final BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ checkService();
+
+ return service.submit(new Callable() {
+ public BufferedImage call() throws Exception {
+ return Scalr.rotate(src, rotation, ops);
+ }
+ });
+ }
+
+ protected static ExecutorService createService() {
+ return createService(new DefaultThreadFactory());
+ }
+
+ protected static ExecutorService createService(ThreadFactory factory)
+ throws IllegalArgumentException {
+ if (factory == null)
+ throw new IllegalArgumentException("factory cannot be null");
+
+ return Executors.newFixedThreadPool(THREAD_COUNT, factory);
+ }
+
+ /**
+ * Used to verify that the underlying service
points at an
+ * active {@link ExecutorService} instance that can be used by this class.
+ *
+ * If service
is null
, has been shutdown or
+ * terminated then this method will replace it with a new
+ * {@link ExecutorService} by calling the {@link #createService()} method
+ * and assigning the returned value to service
.
+ *
+ * Any subclass that wants to customize the {@link ExecutorService} or
+ * {@link ThreadFactory} used internally by this class should override the
+ * {@link #createService()}.
+ */
+ protected static void checkService() {
+ if (service == null || service.isShutdown() || service.isTerminated()) {
+ /*
+ * If service was shutdown or terminated, assigning a new value will
+ * free the reference to the instance, allowing it to be GC'ed when
+ * it is done shutting down (assuming it hadn't already).
+ */
+ service = createService();
+ }
+ }
+
+ /**
+ * Default {@link ThreadFactory} used by the internal
+ * {@link ExecutorService} to creates execution {@link Thread}s for image
+ * scaling.
+ *
+ * More or less a copy of the hidden class backing the
+ * {@link Executors#defaultThreadFactory()} method, but exposed here to make
+ * it easier for implementors to extend and customize.
+ *
+ * @author Doug Lea
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 4.0
+ */
+ protected static class DefaultThreadFactory implements ThreadFactory {
+ protected static final AtomicInteger poolNumber = new AtomicInteger(1);
+
+ protected final ThreadGroup group;
+ protected final AtomicInteger threadNumber = new AtomicInteger(1);
+ protected final String namePrefix;
+
+ DefaultThreadFactory() {
+ SecurityManager manager = System.getSecurityManager();
+
+ /*
+ * Determine the group that threads created by this factory will be
+ * in.
+ */
+ group = (manager == null ? Thread.currentThread().getThreadGroup()
+ : manager.getThreadGroup());
+
+ /*
+ * Define a common name prefix for the threads created by this
+ * factory.
+ */
+ namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
+ }
+
+ /**
+ * Used to create a {@link Thread} capable of executing the given
+ * {@link Runnable}.
+ *
+ * Thread created by this factory are utilized by the parent
+ * {@link ExecutorService} when processing queued up scale operations.
+ */
+ public Thread newThread(Runnable r) {
+ /*
+ * Create a new thread in our specified group with a meaningful
+ * thread name so it is easy to identify.
+ */
+ Thread thread = new Thread(group, r, namePrefix
+ + threadNumber.getAndIncrement(), 0);
+
+ // Configure thread according to class or subclass
+ thread.setDaemon(false);
+ thread.setPriority(Thread.NORM_PRIORITY);
+
+ return thread;
+ }
+ }
+
+ /**
+ * An extension of the {@link DefaultThreadFactory} class that makes two
+ * changes to the execution {@link Thread}s it generations:
+ *
+ * Threads are set to be daemon threads instead of user threads.
+ * Threads execute with a priority of {@link Thread#MIN_PRIORITY} to
+ * make them more compatible with server environment deployments.
+ *
+ * This class is provided as a convenience for subclasses to use if they
+ * want this (common) customization to the {@link Thread}s used internally
+ * by {@link AsyncScalr} to process images, but don't want to have to write
+ * the implementation.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 4.0
+ */
+ protected static class ServerThreadFactory extends DefaultThreadFactory {
+ /**
+ * Overridden to set daemon
property to true
+ * and decrease the priority of the new thread to
+ * {@link Thread#MIN_PRIORITY} before returning it.
+ */
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = super.newThread(r);
+
+ thread.setDaemon(true);
+ thread.setPriority(Thread.MIN_PRIORITY);
+
+ return thread;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/imgscalr/Scalr.java b/src/main/java/org/imgscalr/Scalr.java
new file mode 100644
index 00000000..d08911c1
--- /dev/null
+++ b/src/main/java/org/imgscalr/Scalr.java
@@ -0,0 +1,2349 @@
+/**
+ * Copyright 2011 The Buzz Media, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.imgscalr;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.AreaAveragingScaleFilter;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ColorConvertOp;
+import java.awt.image.ColorModel;
+import java.awt.image.ConvolveOp;
+import java.awt.image.ImagingOpException;
+import java.awt.image.IndexColorModel;
+import java.awt.image.Kernel;
+import java.awt.image.RasterFormatException;
+import java.awt.image.RescaleOp;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Class used to implement performant, high-quality and intelligent image
+ * scaling and manipulation algorithms in native Java 2D.
+ *
+ * This class utilizes the Java2D "best practices" for image manipulation,
+ * ensuring that all operations (even most user-provided {@link BufferedImageOp}
+ * s) are hardware accelerated if provided by the platform and host-VM.
+ *
+ * Image Quality
+ * This class implements a few different methods for scaling an image, providing
+ * either the best-looking result, the fastest result or a balanced result
+ * between the two depending on the scaling hint provided (see {@link Method}).
+ *
+ * This class also implements an optimized version of the incremental scaling
+ * algorithm presented by Chris Campbell in his Perils of
+ * Image.getScaledInstance() article in order to give the best-looking image
+ * resize results (e.g. generating thumbnails that aren't blurry or jagged).
+ *
+ * The results generated by imgscalr using this method, as compared to a single
+ * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} scale operation look much
+ * better, especially when using the {@link Method#ULTRA_QUALITY} method.
+ *
+ * Only when scaling using the {@link Method#AUTOMATIC} method will this class
+ * look at the size of the image before selecting an approach to scaling the
+ * image. If {@link Method#QUALITY} is specified, the best-looking algorithm
+ * possible is always used.
+ *
+ * Minor modifications are made to Campbell's original implementation in the
+ * form of:
+ *
+ * Instead of accepting a user-supplied interpolation method,
+ * {@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} interpolation is always
+ * used. This was done after A/B comparison testing with large images
+ * down-scaled to thumbnail sizes showed noticeable "blurring" when BILINEAR
+ * interpolation was used. Given that Campbell's algorithm is only used in
+ * QUALITY mode when down-scaling, it was determined that the user's expectation
+ * of a much less blurry picture would require that BICUBIC be the default
+ * interpolation in order to meet the QUALITY expectation.
+ * After each iteration of the do-while loop that incrementally scales the
+ * source image down, an explicit effort is made to call
+ * {@link BufferedImage#flush()} on the interim temporary {@link BufferedImage}
+ * instances created by the algorithm in an attempt to ensure a more complete GC
+ * cycle by the VM when cleaning up the temporary instances (this is in addition
+ * to disposing of the temporary {@link Graphics2D} references as well).
+ * Extensive comments have been added to increase readability of the code.
+ * Variable names have been expanded to increase readability of the code.
+ *
+ *
+ * NOTE : This class does not call {@link BufferedImage#flush()}
+ * on any of the source images passed in by calling code; it is up to
+ * the original caller to dispose of their source images when they are no longer
+ * needed so the VM can most efficiently GC them.
+ * Image Proportions
+ * All scaling operations implemented by this class maintain the proportions of
+ * the original image unless a mode of {@link Mode#FIT_EXACT} is specified; in
+ * which case the orientation and proportion of the source image is ignored and
+ * the image is stretched (if necessary) to fit the exact dimensions given.
+ *
+ * When not using {@link Mode#FIT_EXACT}, in order to maintain the
+ * proportionality of the original images, this class implements the following
+ * behavior:
+ *
+ * If the image is LANDSCAPE-oriented or SQUARE, treat the
+ * targetWidth
as the primary dimension and re-calculate the
+ * targetHeight
regardless of what is passed in.
+ * If image is PORTRAIT-oriented, treat the targetHeight
as the
+ * primary dimension and re-calculate the targetWidth
regardless of
+ * what is passed in.
+ * If a {@link Mode} value of {@link Mode#FIT_TO_WIDTH} or
+ * {@link Mode#FIT_TO_HEIGHT} is passed in to the resize
method,
+ * the image's orientation is ignored and the scaled image is fit to the
+ * preferred dimension by using the value passed in by the user for that
+ * dimension and recalculating the other (regardless of image orientation). This
+ * is useful, for example, when working with PORTRAIT oriented images that you
+ * need to all be the same width or visa-versa (e.g. showing user profile
+ * pictures in a directory listing).
+ *
+ * Optimized Image Handling
+ * Java2D provides support for a number of different image types defined as
+ * BufferedImage.TYPE_*
variables, unfortunately not all image
+ * types are supported equally in the Java2D rendering pipeline.
+ *
+ * Some more obscure image types either have poor or no support, leading to
+ * severely degraded quality and processing performance when an attempt is made
+ * by imgscalr to create a scaled instance of the same type as the
+ * source image. In many cases, especially when applying {@link BufferedImageOp}
+ * s, using poorly supported image types can even lead to exceptions or total
+ * corruption of the image (e.g. solid black image).
+ *
+ * imgscalr specifically accounts for and automatically hands
+ * ALL of these pain points for you internally by shuffling all
+ * images into one of two types:
+ *
+ * {@link BufferedImage#TYPE_INT_RGB}
+ * {@link BufferedImage#TYPE_INT_ARGB}
+ *
+ * depending on if the source image utilizes transparency or not. This is a
+ * recommended approach by the Java2D team for dealing with poorly (or non)
+ * supported image types. More can be read about this issue here .
+ *
+ * This is also the reason we recommend using
+ * {@link #apply(BufferedImage, BufferedImageOp...)} to apply your own ops to
+ * images even if you aren't using imgscalr for anything else.
+ * GIF Transparency
+ * Unfortunately in Java 6 and earlier, support for GIF's
+ * {@link IndexColorModel} is sub-par, both in accurate color-selection and in
+ * maintaining transparency when moving to an image of type
+ * {@link BufferedImage#TYPE_INT_ARGB}; because of this issue when a GIF image
+ * is processed by imgscalr and the result saved as a GIF file (instead of PNG),
+ * it is possible to lose the alpha channel of a transparent image or in the
+ * case of applying an optional {@link BufferedImageOp}, lose the entire picture
+ * all together in the result (long standing JDK bugs are filed for all of these
+ * issues).
+ *
+ * imgscalr currently does nothing to work around this manually because it is a
+ * defect in the native platform code itself. Fortunately it looks like the
+ * issues are half-fixed in Java 7 and any manual workarounds we could attempt
+ * internally are relatively expensive, in the form of hand-creating and setting
+ * RGB values pixel-by-pixel with a custom {@link ColorModel} in the scaled
+ * image. This would lead to a very measurable negative impact on performance
+ * without the caller understanding why.
+ *
+ * Workaround : A workaround to this issue with all version of
+ * Java is to simply save a GIF as a PNG; no change to your code needs to be
+ * made except when the image is saved out, e.g. using {@link ImageIO}.
+ *
+ * When a file type of "PNG" is used, both the transparency and high color
+ * quality will be maintained as the PNG code path in Java2D is superior to the
+ * GIF implementation.
+ *
+ * If the issue with optional {@link BufferedImageOp}s destroying GIF image
+ * content is ever fixed in the platform, saving out resulting images as GIFs
+ * should suddenly start working.
+ *
+ * More can be read about the issue here and here .
+ *
Thread Safety
+ * The {@link Scalr} class is thread-safe (as all the methods
+ * are static
); this class maintains no internal state while
+ * performing any of the provided operations and is safe to call simultaneously
+ * from multiple threads.
+ * Logging
+ * This class implements all its debug logging via the
+ * {@link #log(int, String, Object...)} method. At this time logging is done
+ * directly to System.out
via the printf
method. This
+ * allows the logging to be light weight and easy to capture (every imgscalr log
+ * message is prefixed with the {@link #LOG_PREFIX} string) while adding no
+ * dependencies to the library.
+ *
+ * Implementation of logging in this class is as efficient as possible; avoiding
+ * any calls to the logger method or passing of arguments if logging is not
+ * enabled to avoid the (hidden) cost of constructing the Object[] argument for
+ * the varargs-based method call.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 1.1
+ */
+public class Scalr {
+ /**
+ * System property name used to define the debug boolean flag.
+ *
+ * Value is "imgscalr.debug
".
+ */
+ public static final String DEBUG_PROPERTY_NAME = "imgscalr.debug";
+
+ /**
+ * System property name used to define a custom log prefix.
+ *
+ * Value is "imgscalr.logPrefix
".
+ */
+ public static final String LOG_PREFIX_PROPERTY_NAME = "imgscalr.logPrefix";
+
+ /**
+ * Flag used to indicate if debugging output has been enabled by setting the
+ * "imgscalr.debug
" system property to true
. This
+ * value will be false
if the "imgscalr.debug
"
+ * system property is undefined or set to false
.
+ *
+ * This property can be set on startup with:
+ *
+ * -Dimgscalr.debug=true
+ *
or by calling {@link System#setProperty(String, String)} to set a
+ * new property value for {@link #DEBUG_PROPERTY_NAME} before this class is
+ * loaded.
+ *
+ * Default value is false
.
+ */
+ public static final boolean DEBUG = Boolean.getBoolean(DEBUG_PROPERTY_NAME);
+
+ /**
+ * Prefix to every log message this library logs. Using a well-defined
+ * prefix helps make it easier both visually and programmatically to scan
+ * log files for messages produced by this library.
+ *
+ * This property can be set on startup with:
+ *
+ * -Dimgscalr.logPrefix=<YOUR PREFIX HERE>
+ *
or by calling {@link System#setProperty(String, String)} to set a
+ * new property value for {@link #LOG_PREFIX_PROPERTY_NAME} before this
+ * class is loaded.
+ *
+ * Default value is "[imgscalr]
" (including the space).
+ */
+ public static final String LOG_PREFIX = System.getProperty(
+ LOG_PREFIX_PROPERTY_NAME, "[imgscalr] ");
+
+ /**
+ * A {@link ConvolveOp} using a very light "blur" kernel that acts like an
+ * anti-aliasing filter (softens the image a bit) when applied to an image.
+ *
+ * A common request by users of the library was that they wished to "soften"
+ * resulting images when scaling them down drastically. After quite a bit of
+ * A/B testing, the kernel used by this Op was selected as the closest match
+ * for the target which was the softer results from the deprecated
+ * {@link AreaAveragingScaleFilter} (which is used internally by the
+ * deprecated {@link Image#getScaledInstance(int, int, int)} method in the
+ * JDK that imgscalr is meant to replace).
+ *
+ * This ConvolveOp uses a 3x3 kernel with the values:
+ *
+ *
+ * .0f
+ * .08f
+ * .0f
+ *
+ *
+ * .08f
+ * .68f
+ * .08f
+ *
+ *
+ * .0f
+ * .08f
+ * .0f
+ *
+ *
+ *
+ * For those that have worked with ConvolveOps before, this Op uses the
+ * {@link ConvolveOp#EDGE_NO_OP} instruction to not process the pixels along
+ * the very edge of the image (otherwise EDGE_ZERO_FILL would create a
+ * black-border around the image). If you have not worked with a ConvolveOp
+ * before, it just means this default OP will "do the right thing" and not
+ * give you garbage results.
+ *
+ * This ConvolveOp uses no {@link RenderingHints} values as internally the
+ * {@link ConvolveOp} class only uses hints when doing a color conversion
+ * between the source and destination {@link BufferedImage} targets.
+ * imgscalr allows the {@link ConvolveOp} to create its own destination
+ * image every time, so no color conversion is ever needed and thus no
+ * hints.
+ * Performance
+ * Use of this (and other) {@link ConvolveOp}s are hardware accelerated when
+ * possible. For more information on if your image op is hardware
+ * accelerated or not, check the source code of the underlying JDK class
+ * that actually executes the Op code, sun.awt.image.ImagingLib .
+ * Known Issues
+ * In all versions of Java (tested up to Java 7 preview Build 131), running
+ * this op against a GIF with transparency and attempting to save the
+ * resulting image as a GIF results in a corrupted/empty file. The file must
+ * be saved out as a PNG to maintain the transparency.
+ *
+ * @since 3.0
+ */
+ public static final ConvolveOp OP_ANTIALIAS = new ConvolveOp(
+ new Kernel(3, 3, new float[] { .0f, .08f, .0f, .08f, .68f, .08f,
+ .0f, .08f, .0f }), ConvolveOp.EDGE_NO_OP, null);
+
+ /**
+ * A {@link RescaleOp} used to make any input image 10% darker.
+ *
+ * This operation can be applied multiple times in a row if greater than 10%
+ * changes in brightness are desired.
+ *
+ * @since 4.0
+ */
+ public static final RescaleOp OP_DARKER = new RescaleOp(0.9f, 0, null);
+
+ /**
+ * A {@link RescaleOp} used to make any input image 10% brighter.
+ *
+ * This operation can be applied multiple times in a row if greater than 10%
+ * changes in brightness are desired.
+ *
+ * @since 4.0
+ */
+ public static final RescaleOp OP_BRIGHTER = new RescaleOp(1.1f, 0, null);
+
+ /**
+ * A {@link ColorConvertOp} used to convert any image to a grayscale color
+ * palette.
+ *
+ * Applying this op multiple times to the same image has no compounding
+ * effects.
+ *
+ * @since 4.0
+ */
+ public static final ColorConvertOp OP_GRAYSCALE = new ColorConvertOp(
+ ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
+
+ /**
+ * Static initializer used to prepare some of the variables used by this
+ * class.
+ */
+ static {
+ log(0, "Debug output ENABLED");
+ }
+
+ /**
+ * Used to define the different scaling hints that the algorithm can use.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 1.1
+ */
+ public static enum Method {
+ /**
+ * Used to indicate that the scaling implementation should decide which
+ * method to use in order to get the best looking scaled image in the
+ * least amount of time.
+ *
+ * The scaling algorithm will use the
+ * {@link Scalr#THRESHOLD_QUALITY_BALANCED} or
+ * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds as cut-offs to
+ * decide between selecting the QUALITY
,
+ * BALANCED
or SPEED
scaling algorithms.
+ *
+ * By default the thresholds chosen will give nearly the best looking
+ * result in the fastest amount of time. We intend this method to work
+ * for 80% of people looking to scale an image quickly and get a good
+ * looking result.
+ */
+ AUTOMATIC,
+ /**
+ * Used to indicate that the scaling implementation should scale as fast
+ * as possible and return a result. For smaller images (800px in size)
+ * this can result in noticeable aliasing but it can be a few magnitudes
+ * times faster than using the QUALITY method.
+ */
+ SPEED,
+ /**
+ * Used to indicate that the scaling implementation should use a scaling
+ * operation balanced between SPEED and QUALITY. Sometimes SPEED looks
+ * too low quality to be useful (e.g. text can become unreadable when
+ * scaled using SPEED) but using QUALITY mode will increase the
+ * processing time too much. This mode provides a "better than SPEED"
+ * quality in a "less than QUALITY" amount of time.
+ */
+ BALANCED,
+ /**
+ * Used to indicate that the scaling implementation should do everything
+ * it can to create as nice of a result as possible. This approach is
+ * most important for smaller pictures (800px or smaller) and less
+ * important for larger pictures as the difference between this method
+ * and the SPEED method become less and less noticeable as the
+ * source-image size increases. Using the AUTOMATIC method will
+ * automatically prefer the QUALITY method when scaling an image down
+ * below 800px in size.
+ */
+ QUALITY,
+ /**
+ * Used to indicate that the scaling implementation should go above and
+ * beyond the work done by {@link Method#QUALITY} to make the image look
+ * exceptionally good at the cost of more processing time. This is
+ * especially evident when generating thumbnails of images that look
+ * jagged with some of the other {@link Method}s (even
+ * {@link Method#QUALITY}).
+ */
+ ULTRA_QUALITY;
+ }
+
+ /**
+ * Used to define the different modes of resizing that the algorithm can
+ * use.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 3.1
+ */
+ public static enum Mode {
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image by looking at the image's
+ * orientation and generating proportional dimensions that best fit into
+ * the target width and height given
+ *
+ * See "Image Proportions" in the {@link Scalr} class description for
+ * more detail.
+ */
+ AUTOMATIC,
+ /**
+ * Used to fit the image to the exact dimensions given regardless of the
+ * image's proportions. If the dimensions are not proportionally
+ * correct, this will introduce vertical or horizontal stretching to the
+ * image.
+ *
+ * It is recommended that you use one of the other FIT_TO
+ * modes or {@link Mode#AUTOMATIC} if you want the image to look
+ * correct, but if dimension-fitting is the #1 priority regardless of
+ * how it makes the image look, that is what this mode is for.
+ */
+ FIT_EXACT,
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the largest image that fit within the bounding box,
+ * without cropping or distortion, retaining the original proportions.
+ */
+ BEST_FIT_BOTH,
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image that best-fit within the given
+ * width, regardless of the orientation of the image.
+ */
+ FIT_TO_WIDTH,
+ /**
+ * Used to indicate that the scaling implementation should calculate
+ * dimensions for the resultant image that best-fit within the given
+ * height, regardless of the orientation of the image.
+ */
+ FIT_TO_HEIGHT;
+ }
+
+ /**
+ * Used to define the different types of rotations that can be applied to an
+ * image during a resize operation.
+ *
+ * @author Riyad Kalla (software@thebuzzmedia.com)
+ * @since 3.2
+ */
+ public static enum Rotation {
+ /**
+ * 90-degree, clockwise rotation (to the right). This is equivalent to a
+ * quarter-turn of the image to the right; moving the picture on to its
+ * right side.
+ */
+ CW_90,
+ /**
+ * 180-degree, clockwise rotation (to the right). This is equivalent to
+ * 1 half-turn of the image to the right; rotating the picture around
+ * until it is upside down from the original position.
+ */
+ CW_180,
+ /**
+ * 270-degree, clockwise rotation (to the right). This is equivalent to
+ * a quarter-turn of the image to the left; moving the picture on to its
+ * left side.
+ */
+ CW_270,
+ /**
+ * Flip the image horizontally by reflecting it around the y axis.
+ *
+ * This is not a standard rotation around a center point, but instead
+ * creates the mirrored reflection of the image horizontally.
+ *
+ * More specifically, the vertical orientation of the image stays the
+ * same (the top stays on top, and the bottom on bottom), but the right
+ * and left sides flip. This is different than a standard rotation where
+ * the top and bottom would also have been flipped.
+ */
+ FLIP_HORZ,
+ /**
+ * Flip the image vertically by reflecting it around the x axis.
+ *
+ * This is not a standard rotation around a center point, but instead
+ * creates the mirrored reflection of the image vertically.
+ *
+ * More specifically, the horizontal orientation of the image stays the
+ * same (the left stays on the left and the right stays on the right),
+ * but the top and bottom sides flip. This is different than a standard
+ * rotation where the left and right would also have been flipped.
+ */
+ FLIP_VERT;
+ }
+
+ /**
+ * Threshold (in pixels) at which point the scaling operation using the
+ * {@link Method#AUTOMATIC} method will decide if a {@link Method#BALANCED}
+ * method will be used (if smaller than or equal to threshold) or a
+ * {@link Method#SPEED} method will be used (if larger than threshold).
+ *
+ * The bigger the image is being scaled to, the less noticeable degradations
+ * in the image becomes and the faster algorithms can be selected.
+ *
+ * The value of this threshold (1600) was chosen after visual, by-hand, A/B
+ * testing between different types of images scaled with this library; both
+ * photographs and screenshots. It was determined that images below this
+ * size need to use a {@link Method#BALANCED} scale method to look decent in
+ * most all cases while using the faster {@link Method#SPEED} method for
+ * images bigger than this threshold showed no noticeable degradation over a
+ * BALANCED
scale.
+ */
+ public static final int THRESHOLD_BALANCED_SPEED = 1600;
+
+ /**
+ * Threshold (in pixels) at which point the scaling operation using the
+ * {@link Method#AUTOMATIC} method will decide if a {@link Method#QUALITY}
+ * method will be used (if smaller than or equal to threshold) or a
+ * {@link Method#BALANCED} method will be used (if larger than threshold).
+ *
+ * The bigger the image is being scaled to, the less noticeable degradations
+ * in the image becomes and the faster algorithms can be selected.
+ *
+ * The value of this threshold (800) was chosen after visual, by-hand, A/B
+ * testing between different types of images scaled with this library; both
+ * photographs and screenshots. It was determined that images below this
+ * size need to use a {@link Method#QUALITY} scale method to look decent in
+ * most all cases while using the faster {@link Method#BALANCED} method for
+ * images bigger than this threshold showed no noticeable degradation over a
+ * QUALITY
scale.
+ */
+ public static final int THRESHOLD_QUALITY_BALANCED = 800;
+
+ /**
+ * Used to apply, in the order given, 1 or more {@link BufferedImageOp}s to
+ * a given {@link BufferedImage} and return the result.
+ *
+ * Feature : This implementation works around a
+ * decade-old JDK bug that can cause a {@link RasterFormatException}
+ * when applying a perfectly valid {@link BufferedImageOp}s to images.
+ *
+ * Feature : This implementation also works around
+ * {@link BufferedImageOp}s failing to apply and throwing
+ * {@link ImagingOpException}s when run against a src
image
+ * type that is poorly supported. Unfortunately using {@link ImageIO} and
+ * standard Java methods to load images provides no consistency in getting
+ * images in well-supported formats. This method automatically accounts and
+ * corrects for all those problems (if necessary).
+ *
+ * It is recommended you always use this method to apply any
+ * {@link BufferedImageOp}s instead of relying on directly using the
+ * {@link BufferedImageOp#filter(BufferedImage, BufferedImage)} method.
+ *
+ * Performance : Not all {@link BufferedImageOp}s are
+ * hardware accelerated operations, but many of the most popular (like
+ * {@link ConvolveOp}) are. For more information on if your image op is
+ * hardware accelerated or not, check the source code of the underlying JDK
+ * class that actually executes the Op code, sun.awt.image.ImagingLib .
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will have the ops applied to it.
+ * @param ops
+ * 1
or more ops to apply to the image.
+ *
+ * @return a new {@link BufferedImage} that represents the src
+ * with all the given operations applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if ops
is null
or empty.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage apply(BufferedImage src, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = -1;
+ if (DEBUG)
+ t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (ops == null || ops.length == 0)
+ throw new IllegalArgumentException("ops cannot be null or empty");
+
+ int type = src.getType();
+
+ /*
+ * Ensure the src image is in the best supported image type before we
+ * continue, otherwise it is possible our calls below to getBounds2D and
+ * certainly filter(...) may fail if not.
+ *
+ * Java2D makes an attempt at applying most BufferedImageOps using
+ * hardware acceleration via the ImagingLib internal library.
+ *
+ * Unfortunately may of the BufferedImageOp are written to simply fail
+ * with an ImagingOpException if the operation cannot be applied with no
+ * additional information about what went wrong or attempts at
+ * re-applying it in different ways.
+ *
+ * This is assuming the failing BufferedImageOp even returns a null
+ * image after failing to apply; some simply return a corrupted/black
+ * image that result in no exception and it is up to the user to
+ * discover this.
+ *
+ * In internal testing, EVERY failure I've ever seen was the result of
+ * the source image being in a poorly-supported BufferedImage Type like
+ * BGR or ABGR (even though it was loaded with ImageIO).
+ *
+ * To avoid this nasty/stupid surprise with BufferedImageOps, we always
+ * ensure that the src image starts in an optimally supported format
+ * before we try and apply the filter.
+ */
+ if (!(type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB))
+ src = copyToOptimalImage(src);
+
+ if (DEBUG)
+ log(0, "Applying %d BufferedImageOps...", ops.length);
+
+ boolean hasReassignedSrc = false;
+
+ for (int i = 0; i < ops.length; i++) {
+ long subT = -1;
+ if (DEBUG)
+ subT = System.currentTimeMillis();
+ BufferedImageOp op = ops[i];
+
+ // Skip null ops instead of throwing an exception.
+ if (op == null)
+ continue;
+
+ if (DEBUG)
+ log(1, "Applying BufferedImageOp [class=%s, toString=%s]...",
+ op.getClass(), op.toString());
+
+ /*
+ * Must use op.getBounds instead of src.getWidth and src.getHeight
+ * because we are trying to create an image big enough to hold the
+ * result of this operation (which may be to scale the image
+ * smaller), in that case the bounds reported by this op and the
+ * bounds reported by the source image will be different.
+ */
+ Rectangle2D resultBounds = op.getBounds2D(src);
+
+ // Watch out for flaky/misbehaving ops that fail to work right.
+ if (resultBounds == null)
+ throw new ImagingOpException(
+ "BufferedImageOp ["
+ + op.toString()
+ + "] getBounds2D(src) returned null bounds for the target image; this should not happen and indicates a problem with application of this type of op.");
+
+ /*
+ * We must manually create the target image; we cannot rely on the
+ * null-destination filter() method to create a valid destination
+ * for us thanks to this JDK bug that has been filed for almost a
+ * decade:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4965606
+ */
+ BufferedImage dest = createOptimalImage(src,
+ (int) Math.round(resultBounds.getWidth()),
+ (int) Math.round(resultBounds.getHeight()));
+
+ // Perform the operation, update our result to return.
+ BufferedImage result = op.filter(src, dest);
+
+ /*
+ * Flush the 'src' image ONLY IF it is one of our interim temporary
+ * images being used when applying 2 or more operations back to
+ * back. We never want to flush the original image passed in.
+ */
+ if (hasReassignedSrc)
+ src.flush();
+
+ /*
+ * Incase there are more operations to perform, update what we
+ * consider the 'src' reference to our last result so on the next
+ * iteration the next op is applied to this result and not back
+ * against the original src passed in.
+ */
+ src = result;
+
+ /*
+ * Keep track of when we re-assign 'src' to an interim temporary
+ * image, so we know when we can explicitly flush it and clean up
+ * references on future iterations.
+ */
+ hasReassignedSrc = true;
+
+ if (DEBUG)
+ log(1,
+ "Applied BufferedImageOp in %d ms, result [width=%d, height=%d]",
+ System.currentTimeMillis() - subT, result.getWidth(),
+ result.getHeight());
+ }
+
+ if (DEBUG)
+ log(0, "All %d BufferedImageOps applied in %d ms", ops.length,
+ System.currentTimeMillis() - t);
+
+ return src;
+ }
+
+ /**
+ * Used to crop the given src
image from the top-left corner
+ * and applying any optional {@link BufferedImageOp}s to the result before
+ * returning it.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image to crop.
+ * @param width
+ * The width of the bounding cropping box.
+ * @param height
+ * The height of the bounding cropping box.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing the cropped region of
+ * the src
image with any optional operations applied
+ * to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if any coordinates of the bounding crop box is invalid within
+ * the bounds of the src
image (e.g. negative or
+ * too big).
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage crop(BufferedImage src, int width, int height,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return crop(src, 0, 0, width, height, ops);
+ }
+
+ /**
+ * Used to crop the given src
image and apply any optional
+ * {@link BufferedImageOp}s to it before returning the result.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image to crop.
+ * @param x
+ * The x-coordinate of the top-left corner of the bounding box
+ * used for cropping.
+ * @param y
+ * The y-coordinate of the top-left corner of the bounding box
+ * used for cropping.
+ * @param width
+ * The width of the bounding cropping box.
+ * @param height
+ * The height of the bounding cropping box.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing the cropped region of
+ * the src
image with any optional operations applied
+ * to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if any coordinates of the bounding crop box is invalid within
+ * the bounds of the src
image (e.g. negative or
+ * too big).
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage crop(BufferedImage src, int x, int y,
+ int width, int height, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = -1;
+ if (DEBUG)
+ t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (x < 0 || y < 0 || width < 0 || height < 0)
+ throw new IllegalArgumentException("Invalid crop bounds: x [" + x
+ + "], y [" + y + "], width [" + width + "] and height ["
+ + height + "] must all be >= 0");
+
+ int srcWidth = src.getWidth();
+ int srcHeight = src.getHeight();
+
+ if ((x + width) > srcWidth)
+ throw new IllegalArgumentException(
+ "Invalid crop bounds: x + width [" + (x + width)
+ + "] must be <= src.getWidth() [" + srcWidth + "]");
+ if ((y + height) > srcHeight)
+ throw new IllegalArgumentException(
+ "Invalid crop bounds: y + height [" + (y + height)
+ + "] must be <= src.getHeight() [" + srcHeight
+ + "]");
+
+ if (DEBUG)
+ log(0,
+ "Cropping Image [width=%d, height=%d] to [x=%d, y=%d, width=%d, height=%d]...",
+ srcWidth, srcHeight, x, y, width, height);
+
+ // Create a target image of an optimal type to render into.
+ BufferedImage result = createOptimalImage(src, width, height);
+ Graphics g = result.getGraphics();
+
+ /*
+ * Render the region specified by our crop bounds from the src image
+ * directly into our result image (which is the exact size of the crop
+ * region).
+ */
+ g.drawImage(src, 0, 0, width, height, x, y, (x + width), (y + height),
+ null);
+ g.dispose();
+
+ if (DEBUG)
+ log(0, "Cropped Image in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to apply padding around the edges of an image using
+ * {@link Color#BLACK} to fill the extra padded space and then return the
+ * result.
+ *
+ * The amount of padding
specified is applied to all sides;
+ * more specifically, a padding
of 2
would add 2
+ * extra pixels of space (filled by the given color
) on the
+ * top, bottom, left and right sides of the resulting image causing the
+ * result to be 4 pixels wider and 4 pixels taller than the src
+ * image.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image the padding will be added to.
+ * @param padding
+ * The number of pixels of padding to add to each side in the
+ * resulting image. If this value is 0
then
+ * src
is returned unmodified.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing src
with
+ * the given padding applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if padding
is < 1
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage pad(BufferedImage src, int padding,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return pad(src, padding, Color.BLACK);
+ }
+
+ /**
+ * Used to apply padding around the edges of an image using the given color
+ * to fill the extra padded space and then return the result. {@link Color}s
+ * using an alpha channel (i.e. transparency) are supported.
+ *
+ * The amount of padding
specified is applied to all sides;
+ * more specifically, a padding
of 2
would add 2
+ * extra pixels of space (filled by the given color
) on the
+ * top, bottom, left and right sides of the resulting image causing the
+ * result to be 4 pixels wider and 4 pixels taller than the src
+ * image.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image the padding will be added to.
+ * @param padding
+ * The number of pixels of padding to add to each side in the
+ * resulting image. If this value is 0
then
+ * src
is returned unmodified.
+ * @param color
+ * The color to fill the padded space with. {@link Color}s using
+ * an alpha channel (i.e. transparency) are supported.
+ * @param ops
+ * 0
or more ops to apply to the image. If
+ * null
or empty then src
is return
+ * unmodified.
+ *
+ * @return a new {@link BufferedImage} representing src
with
+ * the given padding applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if padding
is < 1
.
+ * @throws IllegalArgumentException
+ * if color
is null
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage pad(BufferedImage src, int padding,
+ Color color, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ long t = -1;
+ if (DEBUG)
+ t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (padding < 1)
+ throw new IllegalArgumentException("padding [" + padding
+ + "] must be > 0");
+ if (color == null)
+ throw new IllegalArgumentException("color cannot be null");
+
+ int srcWidth = src.getWidth();
+ int srcHeight = src.getHeight();
+
+ /*
+ * Double the padding to account for all sides of the image. More
+ * specifically, if padding is "1" we add 2 pixels to width and 2 to
+ * height, so we have 1 new pixel of padding all the way around our
+ * image.
+ */
+ int sizeDiff = (padding * 2);
+ int newWidth = srcWidth + sizeDiff;
+ int newHeight = srcHeight + sizeDiff;
+
+ if (DEBUG)
+ log(0,
+ "Padding Image from [originalWidth=%d, originalHeight=%d, padding=%d] to [newWidth=%d, newHeight=%d]...",
+ srcWidth, srcHeight, padding, newWidth, newHeight);
+
+ boolean colorHasAlpha = (color.getAlpha() != 255);
+ boolean imageHasAlpha = (src.getTransparency() != BufferedImage.OPAQUE);
+
+ BufferedImage result;
+
+ /*
+ * We need to make sure our resulting image that we render into contains
+ * alpha if either our original image OR the padding color we are using
+ * contain it.
+ */
+ if (colorHasAlpha || imageHasAlpha) {
+ if (DEBUG)
+ log(1,
+ "Transparency FOUND in source image or color, using ARGB image type...");
+
+ result = new BufferedImage(newWidth, newHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ } else {
+ if (DEBUG)
+ log(1,
+ "Transparency NOT FOUND in source image or color, using RGB image type...");
+
+ result = new BufferedImage(newWidth, newHeight,
+ BufferedImage.TYPE_INT_RGB);
+ }
+
+ Graphics g = result.getGraphics();
+
+ // "Clear" the background of the new image with our padding color first.
+ g.setColor(color);
+ g.fillRect(0, 0, newWidth, newHeight);
+
+ // Draw the image into the center of the new padded image.
+ g.drawImage(src, padding, padding, null);
+ g.dispose();
+
+ if (DEBUG)
+ log(0, "Padding Applied in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} and mode of
+ * {@link Mode#AUTOMATIC} are used.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage resize(BufferedImage src, int targetSize,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
using the given scaling
+ * method and apply the given {@link BufferedImageOp}s (if any) to the
+ * result before returning it.
+ *
+ * A mode of {@link Mode#AUTOMATIC} is used.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, scalingMethod, Mode.AUTOMATIC, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
(or fitting the image to
+ * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode}
+ * specified) and apply the given {@link BufferedImageOp}s (if any) to the
+ * result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} is used.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Mode resizeMode,
+ int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, resizeMode, targetSize,
+ targetSize, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to a width and
+ * height no bigger than targetSize
(or fitting the image to
+ * the given WIDTH or HEIGHT explicitly, depending on the {@link Mode}
+ * specified) using the given scaling method and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetSize
+ * The target width and height (square) that you wish the image
+ * to fit within.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetSize
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ Mode resizeMode, int targetSize, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, scalingMethod, resizeMode, targetSize, targetSize,
+ ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height and apply the given {@link BufferedImageOp}s (if any) to
+ * the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} and mode of
+ * {@link Mode#AUTOMATIC} are used.
+ *
+ * TIP : See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ */
+ public static BufferedImage resize(BufferedImage src, int targetWidth,
+ int targetHeight, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, Mode.AUTOMATIC, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height using the given scaling method and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A mode of {@link Mode#AUTOMATIC} is used.
+ *
+ * TIP : See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ int targetWidth, int targetHeight, BufferedImageOp... ops) {
+ return resize(src, scalingMethod, Mode.AUTOMATIC, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height (or fitting the image to the given WIDTH or HEIGHT
+ * explicitly, depending on the {@link Mode} specified) and apply the given
+ * {@link BufferedImageOp}s (if any) to the result before returning it.
+ *
+ * A scaling method of {@link Method#AUTOMATIC} is used.
+ *
+ * TIP : See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Mode resizeMode,
+ int targetWidth, int targetHeight, BufferedImageOp... ops)
+ throws IllegalArgumentException, ImagingOpException {
+ return resize(src, Method.AUTOMATIC, resizeMode, targetWidth,
+ targetHeight, ops);
+ }
+
+ /**
+ * Resize a given image (maintaining its original proportion) to the target
+ * width and height (or fitting the image to the given WIDTH or HEIGHT
+ * explicitly, depending on the {@link Mode} specified) using the given
+ * scaling method and apply the given {@link BufferedImageOp}s (if any) to
+ * the result before returning it.
+ *
+ * TIP : See the class description to understand how this
+ * class handles recalculation of the targetWidth
or
+ * targetHeight
depending on the image's orientation in order
+ * to maintain the original proportion.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param scalingMethod
+ * The method used for scaling the image; preferring speed to
+ * quality or a balance of both.
+ * @param resizeMode
+ * Used to indicate how imgscalr should calculate the final
+ * target size for the image, either fitting the image to the
+ * given width ({@link Mode#FIT_TO_WIDTH}) or fitting the image
+ * to the given height ({@link Mode#FIT_TO_HEIGHT}). If
+ * {@link Mode#AUTOMATIC} is passed in, imgscalr will calculate
+ * proportional dimensions for the scaled image based on its
+ * orientation (landscape, square or portrait). Unless you have
+ * very specific size requirements, most of the time you just
+ * want to use {@link Mode#AUTOMATIC} to "do the right thing".
+ * @param targetWidth
+ * The target width that you wish the image to have.
+ * @param targetHeight
+ * The target height that you wish the image to have.
+ * @param ops
+ * 0
or more optional image operations (e.g.
+ * sharpen, blur, etc.) that can be applied to the final result
+ * before returning the image.
+ *
+ * @return a new {@link BufferedImage} representing the scaled
+ * src
image.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if scalingMethod
is null
.
+ * @throws IllegalArgumentException
+ * if resizeMode
is null
.
+ * @throws IllegalArgumentException
+ * if targetWidth
is < 0 or if
+ * targetHeight
is < 0.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Method
+ * @see Mode
+ */
+ public static BufferedImage resize(BufferedImage src, Method scalingMethod,
+ Mode resizeMode, int targetWidth, int targetHeight,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ long t = -1;
+ if (DEBUG)
+ t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (targetWidth < 0)
+ throw new IllegalArgumentException("targetWidth must be >= 0");
+ if (targetHeight < 0)
+ throw new IllegalArgumentException("targetHeight must be >= 0");
+ if (scalingMethod == null)
+ throw new IllegalArgumentException(
+ "scalingMethod cannot be null. A good default value is Method.AUTOMATIC.");
+ if (resizeMode == null)
+ throw new IllegalArgumentException(
+ "resizeMode cannot be null. A good default value is Mode.AUTOMATIC.");
+
+ BufferedImage result = null;
+
+ int currentWidth = src.getWidth();
+ int currentHeight = src.getHeight();
+
+ // <= 1 is a square or landscape-oriented image, > 1 is a portrait.
+ float ratio = ((float) currentHeight / (float) currentWidth);
+
+ if (DEBUG)
+ log(0,
+ "Resizing Image [size=%dx%d, resizeMode=%s, orientation=%s, ratio(H/W)=%f] to [targetSize=%dx%d]",
+ currentWidth, currentHeight, resizeMode,
+ (ratio <= 1 ? "Landscape/Square" : "Portrait"), ratio,
+ targetWidth, targetHeight);
+
+ /*
+ * First determine if ANY size calculation needs to be done, in the case
+ * of FIT_EXACT, ignore image proportions and orientation and just use
+ * what the user sent in, otherwise the proportion of the picture must
+ * be honored.
+ *
+ * The way that is done is to figure out if the image is in a
+ * LANDSCAPE/SQUARE or PORTRAIT orientation and depending on its
+ * orientation, use the primary dimension (width for LANDSCAPE/SQUARE
+ * and height for PORTRAIT) to recalculate the alternative (height and
+ * width respectively) value that adheres to the existing ratio.
+ *
+ * This helps make life easier for the caller as they don't need to
+ * pre-compute proportional dimensions before calling the API, they can
+ * just specify the dimensions they would like the image to roughly fit
+ * within and it will do the right thing without mangling the result.
+ */
+ if (resizeMode == Mode.FIT_EXACT) {
+ if (DEBUG)
+ log(1,
+ "Resize Mode FIT_EXACT used, no width/height checking or re-calculation will be done.");
+ } else if (resizeMode == Mode.BEST_FIT_BOTH) {
+ float requestedHeightScaling = ((float) targetHeight / (float) currentHeight);
+ float requestedWidthScaling = ((float) targetWidth / (float) currentWidth);
+ float actualScaling = Math.min(requestedHeightScaling, requestedWidthScaling);
+
+ targetHeight = Math.round((float) currentHeight * actualScaling);
+ targetWidth = Math.round((float) currentWidth * actualScaling);
+
+ if (targetHeight == currentHeight && targetWidth == currentWidth)
+ return src;
+
+ if (DEBUG)
+ log(1, "Auto-Corrected width and height based on scalingRatio %d.", actualScaling);
+ } else {
+ if ((ratio <= 1 && resizeMode == Mode.AUTOMATIC)
+ || (resizeMode == Mode.FIT_TO_WIDTH)) {
+ // First make sure we need to do any work in the first place
+ if (targetWidth == src.getWidth())
+ return src;
+
+ // Save for detailed logging (this is cheap).
+ int originalTargetHeight = targetHeight;
+
+ /*
+ * Landscape or Square Orientation: Ignore the given height and
+ * re-calculate a proportionally correct value based on the
+ * targetWidth.
+ */
+ targetHeight = Math.round((float) targetWidth * ratio);
+
+ if (DEBUG && originalTargetHeight != targetHeight)
+ log(1,
+ "Auto-Corrected targetHeight [from=%d to=%d] to honor image proportions.",
+ originalTargetHeight, targetHeight);
+ } else {
+ // First make sure we need to do any work in the first place
+ if (targetHeight == src.getHeight())
+ return src;
+
+ // Save for detailed logging (this is cheap).
+ int originalTargetWidth = targetWidth;
+
+ /*
+ * Portrait Orientation: Ignore the given width and re-calculate
+ * a proportionally correct value based on the targetHeight.
+ */
+ targetWidth = Math.round((float) targetHeight / ratio);
+
+ if (DEBUG && originalTargetWidth != targetWidth)
+ log(1,
+ "Auto-Corrected targetWidth [from=%d to=%d] to honor image proportions.",
+ originalTargetWidth, targetWidth);
+ }
+ }
+
+ // If AUTOMATIC was specified, determine the real scaling method.
+ if (scalingMethod == Scalr.Method.AUTOMATIC)
+ scalingMethod = determineScalingMethod(targetWidth, targetHeight,
+ ratio);
+
+ if (DEBUG)
+ log(1, "Using Scaling Method: %s", scalingMethod);
+
+ // Now we scale the image
+ if (scalingMethod == Scalr.Method.SPEED) {
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ } else if (scalingMethod == Scalr.Method.BALANCED) {
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ } else if (scalingMethod == Scalr.Method.QUALITY
+ || scalingMethod == Scalr.Method.ULTRA_QUALITY) {
+ /*
+ * If we are scaling up (in either width or height - since we know
+ * the image will stay proportional we just check if either are
+ * being scaled up), directly using a single BICUBIC will give us
+ * better results then using Chris Campbell's incremental scaling
+ * operation (and take a lot less time).
+ *
+ * If we are scaling down, we must use the incremental scaling
+ * algorithm for the best result.
+ */
+ if (targetWidth > currentWidth || targetHeight > currentHeight) {
+ if (DEBUG)
+ log(1,
+ "QUALITY scale-up, a single BICUBIC scale operation will be used...");
+
+ /*
+ * BILINEAR and BICUBIC look similar the smaller the scale jump
+ * upwards is, if the scale is larger BICUBIC looks sharper and
+ * less fuzzy. But most importantly we have to use BICUBIC to
+ * match the contract of the QUALITY rendering scalingMethod.
+ * This note is just here for anyone reading the code and
+ * wondering how they can speed their own calls up.
+ */
+ result = scaleImage(src, targetWidth, targetHeight,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ } else {
+ if (DEBUG)
+ log(1,
+ "QUALITY scale-down, incremental scaling will be used...");
+
+ /*
+ * Originally we wanted to use BILINEAR interpolation here
+ * because it takes 1/3rd the time that the BICUBIC
+ * interpolation does, however, when scaling large images down
+ * to most sizes bigger than a thumbnail we witnessed noticeable
+ * "softening" in the resultant image with BILINEAR that would
+ * be unexpectedly annoying to a user expecting a "QUALITY"
+ * scale of their original image. Instead BICUBIC was chosen to
+ * honor the contract of a QUALITY scale of the original image.
+ */
+ result = scaleImageIncrementally(src, targetWidth,
+ targetHeight, scalingMethod,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ }
+ }
+
+ if (DEBUG)
+ log(0, "Resized Image in %d ms", System.currentTimeMillis() - t);
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to apply a {@link Rotation} and then 0
or more
+ * {@link BufferedImageOp}s to a given image and return the result.
+ *
+ * TIP : This operation leaves the original src
+ * image unmodified. If the caller is done with the src
image
+ * after getting the result of this operation, remember to call
+ * {@link BufferedImage#flush()} on the src
to free up native
+ * resources and make it easier for the GC to collect the unused image.
+ *
+ * @param src
+ * The image that will have the rotation applied to it.
+ * @param rotation
+ * The rotation that will be applied to the image.
+ * @param ops
+ * Zero or more optional image operations (e.g. sharpen, blur,
+ * etc.) that can be applied to the final result before returning
+ * the image.
+ *
+ * @return a new {@link BufferedImage} representing src
rotated
+ * by the given amount and any optional ops applied to it.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ * @throws IllegalArgumentException
+ * if rotation
is null
.
+ * @throws ImagingOpException
+ * if one of the given {@link BufferedImageOp}s fails to apply.
+ * These exceptions bubble up from the inside of most of the
+ * {@link BufferedImageOp} implementations and are explicitly
+ * defined on the imgscalr API to make it easier for callers to
+ * catch the exception (if they are passing along optional ops
+ * to be applied). imgscalr takes detailed steps to avoid the
+ * most common pitfalls that will cause {@link BufferedImageOp}s
+ * to fail, even when using straight forward JDK-image
+ * operations.
+ *
+ * @see Rotation
+ */
+ public static BufferedImage rotate(BufferedImage src, Rotation rotation,
+ BufferedImageOp... ops) throws IllegalArgumentException,
+ ImagingOpException {
+ long t = -1;
+ if (DEBUG)
+ t = System.currentTimeMillis();
+
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+ if (rotation == null)
+ throw new IllegalArgumentException("rotation cannot be null");
+
+ if (DEBUG)
+ log(0, "Rotating Image [%s]...", rotation);
+
+ /*
+ * Setup the default width/height values from our image.
+ *
+ * In the case of a 90 or 270 (-90) degree rotation, these two values
+ * flip-flop and we will correct those cases down below in the switch
+ * statement.
+ */
+ int newWidth = src.getWidth();
+ int newHeight = src.getHeight();
+
+ /*
+ * We create a transform per operation request as (oddly enough) it ends
+ * up being faster for the VM to create, use and destroy these instances
+ * than it is to re-use a single AffineTransform per-thread via the
+ * AffineTransform.setTo(...) methods which was my first choice (less
+ * object creation); after benchmarking this explicit case and looking
+ * at just how much code gets run inside of setTo() I opted for a new AT
+ * for every rotation.
+ *
+ * Besides the performance win, trying to safely reuse AffineTransforms
+ * via setTo(...) would have required ThreadLocal instances to avoid
+ * race conditions where two or more resize threads are manipulating the
+ * same transform before applying it.
+ *
+ * Misusing ThreadLocals are one of the #1 reasons for memory leaks in
+ * server applications and since we have no nice way to hook into the
+ * init/destroy Servlet cycle or any other initialization cycle for this
+ * library to automatically call ThreadLocal.remove() to avoid the
+ * memory leak, it would have made using this library *safely* on the
+ * server side much harder.
+ *
+ * So we opt for creating individual transforms per rotation op and let
+ * the VM clean them up in a GC. I only clarify all this reasoning here
+ * for anyone else reading this code and being tempted to reuse the AT
+ * instances of performance gains; there aren't any AND you get a lot of
+ * pain along with it.
+ */
+ AffineTransform tx = new AffineTransform();
+
+ switch (rotation) {
+ case CW_90:
+ /*
+ * A 90 or -90 degree rotation will cause the height and width to
+ * flip-flop from the original image to the rotated one.
+ */
+ newWidth = src.getHeight();
+ newHeight = src.getWidth();
+
+ // Reminder: newWidth == result.getHeight() at this point
+ tx.translate(newWidth, 0);
+ tx.rotate(Math.toRadians(90));
+
+ break;
+
+ case CW_270:
+ /*
+ * A 90 or -90 degree rotation will cause the height and width to
+ * flip-flop from the original image to the rotated one.
+ */
+ newWidth = src.getHeight();
+ newHeight = src.getWidth();
+
+ // Reminder: newHeight == result.getWidth() at this point
+ tx.translate(0, newHeight);
+ tx.rotate(Math.toRadians(-90));
+ break;
+
+ case CW_180:
+ tx.translate(newWidth, newHeight);
+ tx.rotate(Math.toRadians(180));
+ break;
+
+ case FLIP_HORZ:
+ tx.translate(newWidth, 0);
+ tx.scale(-1.0, 1.0);
+ break;
+
+ case FLIP_VERT:
+ tx.translate(0, newHeight);
+ tx.scale(1.0, -1.0);
+ break;
+ }
+
+ // Create our target image we will render the rotated result to.
+ BufferedImage result = createOptimalImage(src, newWidth, newHeight);
+ Graphics2D g2d = (Graphics2D) result.createGraphics();
+
+ /*
+ * Render the resultant image to our new rotatedImage buffer, applying
+ * the AffineTransform that we calculated above during rendering so the
+ * pixels from the old position are transposed to the new positions in
+ * the resulting image correctly.
+ */
+ g2d.drawImage(src, tx, null);
+ g2d.dispose();
+
+ if (DEBUG)
+ log(0, "Rotation Applied in %d ms, result [width=%d, height=%d]",
+ System.currentTimeMillis() - t, result.getWidth(),
+ result.getHeight());
+
+ // Apply any optional operations (if specified).
+ if (ops != null && ops.length > 0)
+ result = apply(result, ops);
+
+ return result;
+ }
+
+ /**
+ * Used to write out a useful and well-formatted log message by any piece of
+ * code inside of the imgscalr library.
+ *
+ * If a message cannot be logged (logging is disabled) then this method
+ * returns immediately.
+ *
+ * NOTE : Because Java will auto-box primitive arguments
+ * into Objects when building out the params
array, care should
+ * be taken not to call this method with primitive values unless
+ * {@link Scalr#DEBUG} is true
; otherwise the VM will be
+ * spending time performing unnecessary auto-boxing calculations.
+ *
+ * @param depth
+ * The indentation level of the log message.
+ * @param message
+ * The log message in format string syntax that will be logged.
+ * @param params
+ * The parameters that will be swapped into all the place holders
+ * in the original messages before being logged.
+ *
+ * @see Scalr#LOG_PREFIX
+ * @see Scalr#LOG_PREFIX_PROPERTY_NAME
+ */
+ protected static void log(int depth, String message, Object... params) {
+ if (Scalr.DEBUG) {
+ System.out.print(Scalr.LOG_PREFIX);
+
+ for (int i = 0; i < depth; i++)
+ System.out.print("\t");
+
+ System.out.printf(message, params);
+ System.out.println();
+ }
+ }
+
+ /**
+ * Used to create a {@link BufferedImage} with the most optimal RGB TYPE (
+ * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB}
+ * ) capable of being rendered into from the given src
. The
+ * width and height of both images will be identical.
+ *
+ * This does not perform a copy of the image data from src
into
+ * the result image; see {@link #copyToOptimalImage(BufferedImage)} for
+ * that.
+ *
+ * We force all rendering results into one of these two types, avoiding the
+ * case where a source image is of an unsupported (or poorly supported)
+ * format by Java2D causing the rendering result to end up looking terrible
+ * (common with GIFs) or be totally corrupt (e.g. solid black image).
+ *
+ * Originally reported by Magnus Kvalheim from Movellas when scaling certain
+ * GIF and PNG images.
+ *
+ * @param src
+ * The source image that will be analyzed to determine the most
+ * optimal image type it can be rendered into.
+ *
+ * @return a new {@link BufferedImage} representing the most optimal target
+ * image type that src
can be rendered into.
+ *
+ * @see How
+ * Java2D handles poorly supported image types
+ * @see Thanks
+ * to Morten Nobel for implementation hint
+ */
+ protected static BufferedImage createOptimalImage(BufferedImage src) {
+ return createOptimalImage(src, src.getWidth(), src.getHeight());
+ }
+
+ /**
+ * Used to create a {@link BufferedImage} with the given dimensions and the
+ * most optimal RGB TYPE ( {@link BufferedImage#TYPE_INT_RGB} or
+ * {@link BufferedImage#TYPE_INT_ARGB} ) capable of being rendered into from
+ * the given src
.
+ *
+ * This does not perform a copy of the image data from src
into
+ * the result image; see {@link #copyToOptimalImage(BufferedImage)} for
+ * that.
+ *
+ * We force all rendering results into one of these two types, avoiding the
+ * case where a source image is of an unsupported (or poorly supported)
+ * format by Java2D causing the rendering result to end up looking terrible
+ * (common with GIFs) or be totally corrupt (e.g. solid black image).
+ *
+ * Originally reported by Magnus Kvalheim from Movellas when scaling certain
+ * GIF and PNG images.
+ *
+ * @param src
+ * The source image that will be analyzed to determine the most
+ * optimal image type it can be rendered into.
+ * @param width
+ * The width of the newly created resulting image.
+ * @param height
+ * The height of the newly created resulting image.
+ *
+ * @return a new {@link BufferedImage} representing the most optimal target
+ * image type that src
can be rendered into.
+ *
+ * @throws IllegalArgumentException
+ * if width
or height
are < 0.
+ *
+ * @see How
+ * Java2D handles poorly supported image types
+ * @see Thanks
+ * to Morten Nobel for implementation hint
+ */
+ protected static BufferedImage createOptimalImage(BufferedImage src,
+ int width, int height) throws IllegalArgumentException {
+ if (width < 0 || height < 0)
+ throw new IllegalArgumentException("width [" + width
+ + "] and height [" + height + "] must be >= 0");
+
+ return new BufferedImage(
+ width,
+ height,
+ (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
+ : BufferedImage.TYPE_INT_ARGB));
+ }
+
+ /**
+ * Used to copy a {@link BufferedImage} from a non-optimal type into a new
+ * {@link BufferedImage} instance of an optimal type (RGB or ARGB). If
+ * src
is already of an optimal type, then it is returned
+ * unmodified.
+ *
+ * This method is meant to be used by any calling code (imgscalr's or
+ * otherwise) to convert any inbound image from a poorly supported image
+ * type into the 2 most well-supported image types in Java2D (
+ * {@link BufferedImage#TYPE_INT_RGB} or {@link BufferedImage#TYPE_INT_ARGB}
+ * ) in order to ensure all subsequent graphics operations are performed as
+ * efficiently and correctly as possible.
+ *
+ * When using Java2D to work with image types that are not well supported,
+ * the results can be anything from exceptions bubbling up from the depths
+ * of Java2D to images being completely corrupted and just returned as solid
+ * black.
+ *
+ * @param src
+ * The image to copy (if necessary) into an optimally typed
+ * {@link BufferedImage}.
+ *
+ * @return a representation of the src
image in an optimally
+ * typed {@link BufferedImage}, otherwise src
if it was
+ * already of an optimal type.
+ *
+ * @throws IllegalArgumentException
+ * if src
is null
.
+ */
+ protected static BufferedImage copyToOptimalImage(BufferedImage src)
+ throws IllegalArgumentException {
+ if (src == null)
+ throw new IllegalArgumentException("src cannot be null");
+
+ // Calculate the type depending on the presence of alpha.
+ int type = (src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
+ : BufferedImage.TYPE_INT_ARGB);
+ BufferedImage result = new BufferedImage(src.getWidth(),
+ src.getHeight(), type);
+
+ // Render the src image into our new optimal source.
+ Graphics g = result.getGraphics();
+ g.drawImage(src, 0, 0, null);
+ g.dispose();
+
+ return result;
+ }
+
+ /**
+ * Used to determine the scaling {@link Method} that is best suited for
+ * scaling the image to the targeted dimensions.
+ *
+ * This method is intended to be used to select a specific scaling
+ * {@link Method} when a {@link Method#AUTOMATIC} method is specified. This
+ * method utilizes the {@link Scalr#THRESHOLD_QUALITY_BALANCED} and
+ * {@link Scalr#THRESHOLD_BALANCED_SPEED} thresholds when selecting which
+ * method should be used by comparing the primary dimension (width or
+ * height) against the threshold and seeing where the image falls. The
+ * primary dimension is determined by looking at the orientation of the
+ * image: landscape or square images use their width and portrait-oriented
+ * images use their height.
+ *
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param ratio
+ * A height/width ratio used to determine the orientation of the
+ * image so the primary dimension (width or height) can be
+ * selected to test if it is greater than or less than a
+ * particular threshold.
+ *
+ * @return the fastest {@link Method} suited for scaling the image to the
+ * specified dimensions while maintaining a good-looking result.
+ */
+ protected static Method determineScalingMethod(int targetWidth,
+ int targetHeight, float ratio) {
+ // Get the primary dimension based on the orientation of the image
+ int length = (ratio <= 1 ? targetWidth : targetHeight);
+
+ // Default to speed
+ Method result = Method.SPEED;
+
+ // Figure out which scalingMethod should be used
+ if (length <= Scalr.THRESHOLD_QUALITY_BALANCED)
+ result = Method.QUALITY;
+ else if (length <= Scalr.THRESHOLD_BALANCED_SPEED)
+ result = Method.BALANCED;
+
+ if (DEBUG)
+ log(2, "AUTOMATIC scaling method selected: %s", result.name());
+
+ return result;
+ }
+
+ /**
+ * Used to implement a straight-forward image-scaling operation using Java
+ * 2D.
+ *
+ * This method uses the Oracle-encouraged method of
+ * Graphics2D.drawImage(...)
to scale the given image with the
+ * given interpolation hint.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param interpolationHintValue
+ * The {@link RenderingHints} interpolation value used to
+ * indicate the method that {@link Graphics2D} should use when
+ * scaling the image.
+ *
+ * @return the result of scaling the original src
to the given
+ * dimensions using the given interpolation method.
+ */
+ protected static BufferedImage scaleImage(BufferedImage src,
+ int targetWidth, int targetHeight, Object interpolationHintValue) {
+ // Setup the rendering resources to match the source image's
+ BufferedImage result = createOptimalImage(src, targetWidth,
+ targetHeight);
+ Graphics2D resultGraphics = result.createGraphics();
+
+ // Scale the image to the new buffer using the specified rendering hint.
+ resultGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ interpolationHintValue);
+ resultGraphics.drawImage(src, 0, 0, targetWidth, targetHeight, null);
+
+ // Just to be clean, explicitly dispose our temporary graphics object
+ resultGraphics.dispose();
+
+ // Return the scaled image to the caller.
+ return result;
+ }
+
+ /**
+ * Used to implement Chris Campbell's incremental-scaling algorithm: http://today.java.net/pub/a/today/2007/04/03/perils
+ * -of-image-getscaledinstance.html .
+ *
+ * Modifications to the original algorithm are variable names and comments
+ * added for clarity and the hard-coding of using BICUBIC interpolation as
+ * well as the explicit "flush()" operation on the interim BufferedImage
+ * instances to avoid resource leaking.
+ *
+ * @param src
+ * The image that will be scaled.
+ * @param targetWidth
+ * The target width for the scaled image.
+ * @param targetHeight
+ * The target height for the scaled image.
+ * @param scalingMethod
+ * The scaling method specified by the user (or calculated by
+ * imgscalr) to use for this incremental scaling operation.
+ * @param interpolationHintValue
+ * The {@link RenderingHints} interpolation value used to
+ * indicate the method that {@link Graphics2D} should use when
+ * scaling the image.
+ *
+ * @return an image scaled to the given dimensions using the given rendering
+ * hint.
+ */
+ protected static BufferedImage scaleImageIncrementally(BufferedImage src,
+ int targetWidth, int targetHeight, Method scalingMethod,
+ Object interpolationHintValue) {
+ boolean hasReassignedSrc = false;
+ int incrementCount = 0;
+ int currentWidth = src.getWidth();
+ int currentHeight = src.getHeight();
+
+ /*
+ * The original QUALITY mode, representing Chris Campbell's algorithm,
+ * is to step down by 1/2s every time when scaling the image
+ * incrementally. Users pointed out that using this method to scale
+ * images with noticeable straight lines left them really jagged in
+ * smaller thumbnail format.
+ *
+ * After investigation it was discovered that scaling incrementally by
+ * smaller increments was the ONLY way to make the thumbnail sized
+ * images look less jagged and more accurate; almost matching the
+ * accuracy of Mac's built in thumbnail generation which is the highest
+ * quality resize I've come across (better than GIMP Lanczos3 and
+ * Windows 7).
+ *
+ * A divisor of 7 was chose as using 5 still left some jaggedness in the
+ * image while a divisor of 8 or higher made the resulting thumbnail too
+ * soft; like our OP_ANTIALIAS convolve op had been forcibly applied to
+ * the result even if the user didn't want it that soft.
+ *
+ * Using a divisor of 7 for the ULTRA_QUALITY seemed to be the sweet
+ * spot.
+ *
+ * NOTE: Below when the actual fraction is used to calculate the small
+ * portion to subtract from the current dimension, this is a
+ * progressively smaller and smaller chunk. When the code was changed to
+ * do a linear reduction of the image of equal steps for each
+ * incremental resize (e.g. say 50px each time) the result was
+ * significantly worse than the progressive approach used below; even
+ * when a very high number of incremental steps (13) was tested.
+ */
+ int fraction = (scalingMethod == Method.ULTRA_QUALITY ? 7 : 2);
+
+ do {
+ int prevCurrentWidth = currentWidth;
+ int prevCurrentHeight = currentHeight;
+
+ /*
+ * If the current width is bigger than our target, cut it in half
+ * and sample again.
+ */
+ if (currentWidth > targetWidth) {
+ currentWidth -= (currentWidth / fraction);
+
+ /*
+ * If we cut the width too far it means we are on our last
+ * iteration. Just set it to the target width and finish up.
+ */
+ if (currentWidth < targetWidth)
+ currentWidth = targetWidth;
+ }
+
+ /*
+ * If the current height is bigger than our target, cut it in half
+ * and sample again.
+ */
+
+ if (currentHeight > targetHeight) {
+ currentHeight -= (currentHeight / fraction);
+
+ /*
+ * If we cut the height too far it means we are on our last
+ * iteration. Just set it to the target height and finish up.
+ */
+
+ if (currentHeight < targetHeight)
+ currentHeight = targetHeight;
+ }
+
+ /*
+ * Stop when we cannot incrementally step down anymore.
+ *
+ * This used to use a || condition, but that would cause problems
+ * when using FIT_EXACT such that sometimes the width OR height
+ * would not change between iterations, but the other dimension
+ * would (e.g. resizing 500x500 to 500x250).
+ *
+ * Now changing this to an && condition requires that both
+ * dimensions do not change between a resize iteration before we
+ * consider ourselves done.
+ */
+ if (prevCurrentWidth == currentWidth
+ && prevCurrentHeight == currentHeight)
+ break;
+
+ if (DEBUG)
+ log(2, "Scaling from [%d x %d] to [%d x %d]", prevCurrentWidth,
+ prevCurrentHeight, currentWidth, currentHeight);
+
+ // Render the incremental scaled image.
+ BufferedImage incrementalImage = scaleImage(src, currentWidth,
+ currentHeight, interpolationHintValue);
+
+ /*
+ * Before re-assigning our interim (partially scaled)
+ * incrementalImage to be the new src image before we iterate around
+ * again to process it down further, we want to flush() the previous
+ * src image IF (and only IF) it was one of our own temporary
+ * BufferedImages created during this incremental down-sampling
+ * cycle. If it wasn't one of ours, then it was the original
+ * caller-supplied BufferedImage in which case we don't want to
+ * flush() it and just leave it alone.
+ */
+ if (hasReassignedSrc)
+ src.flush();
+
+ /*
+ * Now treat our incremental partially scaled image as the src image
+ * and cycle through our loop again to do another incremental
+ * scaling of it (if necessary).
+ */
+ src = incrementalImage;
+
+ /*
+ * Keep track of us re-assigning the original caller-supplied source
+ * image with one of our interim BufferedImages so we know when to
+ * explicitly flush the interim "src" on the next cycle through.
+ */
+ hasReassignedSrc = true;
+
+ // Track how many times we go through this cycle to scale the image.
+ incrementCount++;
+ } while (currentWidth != targetWidth || currentHeight != targetHeight);
+
+ if (DEBUG)
+ log(2, "Incrementally Scaled Image in %d steps.", incrementCount);
+
+ /*
+ * Once the loop has exited, the src image argument is now our scaled
+ * result image that we want to return.
+ */
+ return src;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/objectweb/asm/AnnotationVisitor.java b/src/main/java/org/objectweb/asm/AnnotationVisitor.java
new file mode 100644
index 00000000..b6440837
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/AnnotationVisitor.java
@@ -0,0 +1,169 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A visitor to visit a Java annotation. The methods of this class must be
+ * called in the following order: ( visit | visitEnum |
+ * visitAnnotation | visitArray )* visitEnd .
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public abstract class AnnotationVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The annotation visitor to which this visitor must delegate method calls.
+ * May be null.
+ */
+ protected AnnotationVisitor av;
+
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public AnnotationVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param av
+ * the annotation visitor to which this visitor must delegate
+ * method calls. May be null.
+ */
+ public AnnotationVisitor(final int api, final AnnotationVisitor av) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.av = av;
+ }
+
+ /**
+ * Visits a primitive value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param value
+ * the actual value, whose type must be {@link Byte},
+ * {@link Boolean}, {@link Character}, {@link Short},
+ * {@link Integer} , {@link Long}, {@link Float}, {@link Double},
+ * {@link String} or {@link Type} or OBJECT or ARRAY sort. This
+ * value can also be an array of byte, boolean, short, char, int,
+ * long, float or double values (this is equivalent to using
+ * {@link #visitArray visitArray} and visiting each array element
+ * in turn, but is more convenient).
+ */
+ public void visit(String name, Object value) {
+ if (av != null) {
+ av.visit(name, value);
+ }
+ }
+
+ /**
+ * Visits an enumeration value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param desc
+ * the class descriptor of the enumeration class.
+ * @param value
+ * the actual enumeration value.
+ */
+ public void visitEnum(String name, String desc, String value) {
+ if (av != null) {
+ av.visitEnum(name, desc, value);
+ }
+ }
+
+ /**
+ * Visits a nested annotation value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param desc
+ * the class descriptor of the nested annotation class.
+ * @return a visitor to visit the actual nested annotation value, or
+ * null if this visitor is not interested in visiting this
+ * nested annotation. The nested annotation value must be fully
+ * visited before calling other methods on this annotation
+ * visitor .
+ */
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ if (av != null) {
+ return av.visitAnnotation(name, desc);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an array value of the annotation. Note that arrays of primitive
+ * types (such as byte, boolean, short, char, int, long, float or double)
+ * can be passed as value to {@link #visit visit}. This is what
+ * {@link ClassReader} does.
+ *
+ * @param name
+ * the value name.
+ * @return a visitor to visit the actual array value elements, or
+ * null if this visitor is not interested in visiting these
+ * values. The 'name' parameters passed to the methods of this
+ * visitor are ignored. All the array values must be visited
+ * before calling other methods on this annotation visitor .
+ */
+ public AnnotationVisitor visitArray(String name) {
+ if (av != null) {
+ return av.visitArray(name);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the end of the annotation.
+ */
+ public void visitEnd() {
+ if (av != null) {
+ av.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/AnnotationWriter.java b/src/main/java/org/objectweb/asm/AnnotationWriter.java
new file mode 100644
index 00000000..6b95608a
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/AnnotationWriter.java
@@ -0,0 +1,371 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * An {@link AnnotationVisitor} that generates annotations in bytecode form.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+final class AnnotationWriter extends AnnotationVisitor {
+
+ /**
+ * The class writer to which this annotation must be added.
+ */
+ private final ClassWriter cw;
+
+ /**
+ * The number of values in this annotation.
+ */
+ private int size;
+
+ /**
+ * true if values are named, false otherwise. Annotation
+ * writers used for annotation default and annotation arrays use unnamed
+ * values.
+ */
+ private final boolean named;
+
+ /**
+ * The annotation values in bytecode form. This byte vector only contains
+ * the values themselves, i.e. the number of values must be stored as a
+ * unsigned short just before these bytes.
+ */
+ private final ByteVector bv;
+
+ /**
+ * The byte vector to be used to store the number of values of this
+ * annotation. See {@link #bv}.
+ */
+ private final ByteVector parent;
+
+ /**
+ * Where the number of values of this annotation must be stored in
+ * {@link #parent}.
+ */
+ private final int offset;
+
+ /**
+ * Next annotation writer. This field is used to store annotation lists.
+ */
+ AnnotationWriter next;
+
+ /**
+ * Previous annotation writer. This field is used to store annotation lists.
+ */
+ AnnotationWriter prev;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link AnnotationWriter}.
+ *
+ * @param cw
+ * the class writer to which this annotation must be added.
+ * @param named
+ * true if values are named, false otherwise.
+ * @param bv
+ * where the annotation values must be stored.
+ * @param parent
+ * where the number of annotation values must be stored.
+ * @param offset
+ * where in parent the number of annotation values must
+ * be stored.
+ */
+ AnnotationWriter(final ClassWriter cw, final boolean named,
+ final ByteVector bv, final ByteVector parent, final int offset) {
+ super(Opcodes.ASM5);
+ this.cw = cw;
+ this.named = named;
+ this.bv = bv;
+ this.parent = parent;
+ this.offset = offset;
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the AnnotationVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void visit(final String name, final Object value) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ if (value instanceof String) {
+ bv.put12('s', cw.newUTF8((String) value));
+ } else if (value instanceof Byte) {
+ bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index);
+ } else if (value instanceof Boolean) {
+ int v = ((Boolean) value).booleanValue() ? 1 : 0;
+ bv.put12('Z', cw.newInteger(v).index);
+ } else if (value instanceof Character) {
+ bv.put12('C', cw.newInteger(((Character) value).charValue()).index);
+ } else if (value instanceof Short) {
+ bv.put12('S', cw.newInteger(((Short) value).shortValue()).index);
+ } else if (value instanceof Type) {
+ bv.put12('c', cw.newUTF8(((Type) value).getDescriptor()));
+ } else if (value instanceof byte[]) {
+ byte[] v = (byte[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('B', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof boolean[]) {
+ boolean[] v = (boolean[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index);
+ }
+ } else if (value instanceof short[]) {
+ short[] v = (short[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('S', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof char[]) {
+ char[] v = (char[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('C', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof int[]) {
+ int[] v = (int[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('I', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof long[]) {
+ long[] v = (long[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('J', cw.newLong(v[i]).index);
+ }
+ } else if (value instanceof float[]) {
+ float[] v = (float[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('F', cw.newFloat(v[i]).index);
+ }
+ } else if (value instanceof double[]) {
+ double[] v = (double[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('D', cw.newDouble(v[i]).index);
+ }
+ } else {
+ Item i = cw.newConstItem(value);
+ bv.put12(".s.IFJDCS".charAt(i.type), i.index);
+ }
+ }
+
+ @Override
+ public void visitEnum(final String name, final String desc,
+ final String value) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String name,
+ final String desc) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ // write tag and type, and reserve space for values count
+ bv.put12('@', cw.newUTF8(desc)).putShort(0);
+ return new AnnotationWriter(cw, true, bv, bv, bv.length - 2);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(final String name) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ // write tag, and reserve space for array size
+ bv.put12('[', 0);
+ return new AnnotationWriter(cw, false, bv, bv, bv.length - 2);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (parent != null) {
+ byte[] data = parent.data;
+ data[offset] = (byte) (size >>> 8);
+ data[offset + 1] = (byte) size;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of this annotation writer list.
+ *
+ * @return the size of this annotation writer list.
+ */
+ int getSize() {
+ int size = 0;
+ AnnotationWriter aw = this;
+ while (aw != null) {
+ size += aw.bv.length;
+ aw = aw.next;
+ }
+ return size;
+ }
+
+ /**
+ * Puts the annotations of this annotation writer list into the given byte
+ * vector.
+ *
+ * @param out
+ * where the annotations must be put.
+ */
+ void put(final ByteVector out) {
+ int n = 0;
+ int size = 2;
+ AnnotationWriter aw = this;
+ AnnotationWriter last = null;
+ while (aw != null) {
+ ++n;
+ size += aw.bv.length;
+ aw.visitEnd(); // in case user forgot to call visitEnd
+ aw.prev = last;
+ last = aw;
+ aw = aw.next;
+ }
+ out.putInt(size);
+ out.putShort(n);
+ aw = last;
+ while (aw != null) {
+ out.putByteArray(aw.bv.data, 0, aw.bv.length);
+ aw = aw.prev;
+ }
+ }
+
+ /**
+ * Puts the given annotation lists into the given byte vector.
+ *
+ * @param panns
+ * an array of annotation writer lists.
+ * @param off
+ * index of the first annotation to be written.
+ * @param out
+ * where the annotations must be put.
+ */
+ static void put(final AnnotationWriter[] panns, final int off,
+ final ByteVector out) {
+ int size = 1 + 2 * (panns.length - off);
+ for (int i = off; i < panns.length; ++i) {
+ size += panns[i] == null ? 0 : panns[i].getSize();
+ }
+ out.putInt(size).putByte(panns.length - off);
+ for (int i = off; i < panns.length; ++i) {
+ AnnotationWriter aw = panns[i];
+ AnnotationWriter last = null;
+ int n = 0;
+ while (aw != null) {
+ ++n;
+ aw.visitEnd(); // in case user forgot to call visitEnd
+ aw.prev = last;
+ last = aw;
+ aw = aw.next;
+ }
+ out.putShort(n);
+ aw = last;
+ while (aw != null) {
+ out.putByteArray(aw.bv.data, 0, aw.bv.length);
+ aw = aw.prev;
+ }
+ }
+ }
+
+ /**
+ * Puts the given type reference and type path into the given bytevector.
+ * LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported.
+ *
+ * @param typeRef
+ * a reference to the annotated type. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param out
+ * where the type reference and type path must be put.
+ */
+ static void putTarget(int typeRef, TypePath typePath, ByteVector out) {
+ switch (typeRef >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ out.putShort(typeRef >>> 16);
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ out.putByte(typeRef >>> 24);
+ break;
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ out.putInt(typeRef);
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ out.put12(typeRef >>> 24, (typeRef & 0xFFFF00) >> 8);
+ break;
+ }
+ if (typePath == null) {
+ out.putByte(0);
+ } else {
+ int length = typePath.b[typePath.offset] * 2 + 1;
+ out.putByteArray(typePath.b, typePath.offset, length);
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Attribute.java b/src/main/java/org/objectweb/asm/Attribute.java
new file mode 100644
index 00000000..8a2a882f
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Attribute.java
@@ -0,0 +1,255 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A non standard class, field, method or code attribute.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public class Attribute {
+
+ /**
+ * The type of this attribute.
+ */
+ public final String type;
+
+ /**
+ * The raw value of this attribute, used only for unknown attributes.
+ */
+ byte[] value;
+
+ /**
+ * The next attribute in this attribute list. May be null .
+ */
+ Attribute next;
+
+ /**
+ * Constructs a new empty attribute.
+ *
+ * @param type
+ * the type of the attribute.
+ */
+ protected Attribute(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns true if this type of attribute is unknown. The default
+ * implementation of this method always returns true .
+ *
+ * @return true if this type of attribute is unknown.
+ */
+ public boolean isUnknown() {
+ return true;
+ }
+
+ /**
+ * Returns true if this type of attribute is a code attribute.
+ *
+ * @return true if this type of attribute is a code attribute.
+ */
+ public boolean isCodeAttribute() {
+ return false;
+ }
+
+ /**
+ * Returns the labels corresponding to this attribute.
+ *
+ * @return the labels corresponding to this attribute, or null if
+ * this attribute is not a code attribute that contains labels.
+ */
+ protected Label[] getLabels() {
+ return null;
+ }
+
+ /**
+ * Reads a {@link #type type} attribute. This method must return a
+ * new {@link Attribute} object, of type {@link #type type},
+ * corresponding to the len bytes starting at the given offset, in
+ * the given class reader.
+ *
+ * @param cr
+ * the class that contains the attribute to be read.
+ * @param off
+ * index of the first byte of the attribute's content in
+ * {@link ClassReader#b cr.b}. The 6 attribute header bytes,
+ * containing the type and the length of the attribute, are not
+ * taken into account here.
+ * @param len
+ * the length of the attribute's content.
+ * @param buf
+ * buffer to be used to call {@link ClassReader#readUTF8
+ * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass}
+ * or {@link ClassReader#readConst readConst}.
+ * @param codeOff
+ * index of the first byte of code's attribute content in
+ * {@link ClassReader#b cr.b}, or -1 if the attribute to be read
+ * is not a code attribute. The 6 attribute header bytes,
+ * containing the type and the length of the attribute, are not
+ * taken into account here.
+ * @param labels
+ * the labels of the method's code, or null if the
+ * attribute to be read is not a code attribute.
+ * @return a new {@link Attribute} object corresponding to the given
+ * bytes.
+ */
+ protected Attribute read(final ClassReader cr, final int off,
+ final int len, final char[] buf, final int codeOff,
+ final Label[] labels) {
+ Attribute attr = new Attribute(type);
+ attr.value = new byte[len];
+ System.arraycopy(cr.b, off, attr.value, 0, len);
+ return attr;
+ }
+
+ /**
+ * Returns the byte array form of this attribute.
+ *
+ * @param cw
+ * the class to which this attribute must be added. This
+ * parameter can be used to add to the constant pool of this
+ * class the items that corresponds to this attribute.
+ * @param code
+ * the bytecode of the method corresponding to this code
+ * attribute, or null if this attribute is not a code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to this
+ * code attribute, or null if this attribute is not a
+ * code attribute.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to this
+ * code attribute, or -1 if this attribute is not a code
+ * attribute.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to this code attribute, or -1 if this attribute
+ * is not a code attribute.
+ * @return the byte array form of this attribute.
+ */
+ protected ByteVector write(final ClassWriter cw, final byte[] code,
+ final int len, final int maxStack, final int maxLocals) {
+ ByteVector v = new ByteVector();
+ v.data = value;
+ v.length = value.length;
+ return v;
+ }
+
+ /**
+ * Returns the length of the attribute list that begins with this attribute.
+ *
+ * @return the length of the attribute list that begins with this attribute.
+ */
+ final int getCount() {
+ int count = 0;
+ Attribute attr = this;
+ while (attr != null) {
+ count += 1;
+ attr = attr.next;
+ }
+ return count;
+ }
+
+ /**
+ * Returns the size of all the attributes in this attribute list.
+ *
+ * @param cw
+ * the class writer to be used to convert the attributes into
+ * byte arrays, with the {@link #write write} method.
+ * @param code
+ * the bytecode of the method corresponding to these code
+ * attributes, or null if these attributes are not code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to
+ * these code attributes, or null if these attributes
+ * are not code attributes.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to these
+ * code attributes, or -1 if these attributes are not code
+ * attributes.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to these code attributes, or -1 if these
+ * attributes are not code attributes.
+ * @return the size of all the attributes in this attribute list. This size
+ * includes the size of the attribute headers.
+ */
+ final int getSize(final ClassWriter cw, final byte[] code, final int len,
+ final int maxStack, final int maxLocals) {
+ Attribute attr = this;
+ int size = 0;
+ while (attr != null) {
+ cw.newUTF8(attr.type);
+ size += attr.write(cw, code, len, maxStack, maxLocals).length + 6;
+ attr = attr.next;
+ }
+ return size;
+ }
+
+ /**
+ * Writes all the attributes of this attribute list in the given byte
+ * vector.
+ *
+ * @param cw
+ * the class writer to be used to convert the attributes into
+ * byte arrays, with the {@link #write write} method.
+ * @param code
+ * the bytecode of the method corresponding to these code
+ * attributes, or null if these attributes are not code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to
+ * these code attributes, or null if these attributes
+ * are not code attributes.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to these
+ * code attributes, or -1 if these attributes are not code
+ * attributes.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to these code attributes, or -1 if these
+ * attributes are not code attributes.
+ * @param out
+ * where the attributes must be written.
+ */
+ final void put(final ClassWriter cw, final byte[] code, final int len,
+ final int maxStack, final int maxLocals, final ByteVector out) {
+ Attribute attr = this;
+ while (attr != null) {
+ ByteVector b = attr.write(cw, code, len, maxStack, maxLocals);
+ out.putShort(cw.newUTF8(attr.type)).putInt(b.length);
+ out.putByteArray(b.data, 0, b.length);
+ attr = attr.next;
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/ByteVector.java b/src/main/java/org/objectweb/asm/ByteVector.java
new file mode 100644
index 00000000..9c532be7
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/ByteVector.java
@@ -0,0 +1,339 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A dynamically extensible vector of bytes. This class is roughly equivalent to
+ * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient.
+ *
+ * @author Eric Bruneton
+ */
+public class ByteVector {
+
+ /**
+ * The content of this vector.
+ */
+ byte[] data;
+
+ /**
+ * Actual number of bytes in this vector.
+ */
+ int length;
+
+ /**
+ * Constructs a new {@link ByteVector ByteVector} with a default initial
+ * size.
+ */
+ public ByteVector() {
+ data = new byte[64];
+ }
+
+ /**
+ * Constructs a new {@link ByteVector ByteVector} with the given initial
+ * size.
+ *
+ * @param initialSize
+ * the initial size of the byte vector to be constructed.
+ */
+ public ByteVector(final int initialSize) {
+ data = new byte[initialSize];
+ }
+
+ /**
+ * Puts a byte into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param b
+ * a byte.
+ * @return this byte vector.
+ */
+ public ByteVector putByte(final int b) {
+ int length = this.length;
+ if (length + 1 > data.length) {
+ enlarge(1);
+ }
+ data[length++] = (byte) b;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts two bytes into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param b1
+ * a byte.
+ * @param b2
+ * another byte.
+ * @return this byte vector.
+ */
+ ByteVector put11(final int b1, final int b2) {
+ int length = this.length;
+ if (length + 2 > data.length) {
+ enlarge(2);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) b1;
+ data[length++] = (byte) b2;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a short into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param s
+ * a short.
+ * @return this byte vector.
+ */
+ public ByteVector putShort(final int s) {
+ int length = this.length;
+ if (length + 2 > data.length) {
+ enlarge(2);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) (s >>> 8);
+ data[length++] = (byte) s;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a byte and a short into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param b
+ * a byte.
+ * @param s
+ * a short.
+ * @return this byte vector.
+ */
+ ByteVector put12(final int b, final int s) {
+ int length = this.length;
+ if (length + 3 > data.length) {
+ enlarge(3);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) b;
+ data[length++] = (byte) (s >>> 8);
+ data[length++] = (byte) s;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts an int into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param i
+ * an int.
+ * @return this byte vector.
+ */
+ public ByteVector putInt(final int i) {
+ int length = this.length;
+ if (length + 4 > data.length) {
+ enlarge(4);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a long into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param l
+ * a long.
+ * @return this byte vector.
+ */
+ public ByteVector putLong(final long l) {
+ int length = this.length;
+ if (length + 8 > data.length) {
+ enlarge(8);
+ }
+ byte[] data = this.data;
+ int i = (int) (l >>> 32);
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ i = (int) l;
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param s
+ * a String whose UTF8 encoded length must be less than 65536.
+ * @return this byte vector.
+ */
+ public ByteVector putUTF8(final String s) {
+ int charLength = s.length();
+ if (charLength > 65535) {
+ throw new IllegalArgumentException();
+ }
+ int len = length;
+ if (len + 2 + charLength > data.length) {
+ enlarge(2 + charLength);
+ }
+ byte[] data = this.data;
+ // optimistic algorithm: instead of computing the byte length and then
+ // serializing the string (which requires two loops), we assume the byte
+ // length is equal to char length (which is the most frequent case), and
+ // we start serializing the string right away. During the serialization,
+ // if we find that this assumption is wrong, we continue with the
+ // general method.
+ data[len++] = (byte) (charLength >>> 8);
+ data[len++] = (byte) charLength;
+ for (int i = 0; i < charLength; ++i) {
+ char c = s.charAt(i);
+ if (c >= '\001' && c <= '\177') {
+ data[len++] = (byte) c;
+ } else {
+ length = len;
+ return encodeUTF8(s, i, 65535);
+ }
+ }
+ length = len;
+ return this;
+ }
+
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is
+ * automatically enlarged if necessary. The string length is encoded in two
+ * bytes before the encoded characters, if there is space for that (i.e. if
+ * this.length - i - 2 >= 0).
+ *
+ * @param s
+ * the String to encode.
+ * @param i
+ * the index of the first character to encode. The previous
+ * characters are supposed to have already been encoded, using
+ * only one byte per character.
+ * @param maxByteLength
+ * the maximum byte length of the encoded string, including the
+ * already encoded characters.
+ * @return this byte vector.
+ */
+ ByteVector encodeUTF8(final String s, int i, int maxByteLength) {
+ int charLength = s.length();
+ int byteLength = i;
+ char c;
+ for (int j = i; j < charLength; ++j) {
+ c = s.charAt(j);
+ if (c >= '\001' && c <= '\177') {
+ byteLength++;
+ } else if (c > '\u07FF') {
+ byteLength += 3;
+ } else {
+ byteLength += 2;
+ }
+ }
+ if (byteLength > maxByteLength) {
+ throw new IllegalArgumentException();
+ }
+ int start = length - i - 2;
+ if (start >= 0) {
+ data[start] = (byte) (byteLength >>> 8);
+ data[start + 1] = (byte) byteLength;
+ }
+ if (length + byteLength - i > data.length) {
+ enlarge(byteLength - i);
+ }
+ int len = length;
+ for (int j = i; j < charLength; ++j) {
+ c = s.charAt(j);
+ if (c >= '\001' && c <= '\177') {
+ data[len++] = (byte) c;
+ } else if (c > '\u07FF') {
+ data[len++] = (byte) (0xE0 | c >> 12 & 0xF);
+ data[len++] = (byte) (0x80 | c >> 6 & 0x3F);
+ data[len++] = (byte) (0x80 | c & 0x3F);
+ } else {
+ data[len++] = (byte) (0xC0 | c >> 6 & 0x1F);
+ data[len++] = (byte) (0x80 | c & 0x3F);
+ }
+ }
+ length = len;
+ return this;
+ }
+
+ /**
+ * Puts an array of bytes into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param b
+ * an array of bytes. May be null to put len
+ * null bytes into this byte vector.
+ * @param off
+ * index of the fist byte of b that must be copied.
+ * @param len
+ * number of bytes of b that must be copied.
+ * @return this byte vector.
+ */
+ public ByteVector putByteArray(final byte[] b, final int off, final int len) {
+ if (length + len > data.length) {
+ enlarge(len);
+ }
+ if (b != null) {
+ System.arraycopy(b, off, data, length, len);
+ }
+ length += len;
+ return this;
+ }
+
+ /**
+ * Enlarge this byte vector so that it can receive n more bytes.
+ *
+ * @param size
+ * number of additional bytes that this byte vector should be
+ * able to receive.
+ */
+ private void enlarge(final int size) {
+ int length1 = 2 * data.length;
+ int length2 = length + size;
+ byte[] newData = new byte[length1 > length2 ? length1 : length2];
+ System.arraycopy(data, 0, newData, 0, length);
+ data = newData;
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/ClassReader.java b/src/main/java/org/objectweb/asm/ClassReader.java
new file mode 100644
index 00000000..e23fd60c
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/ClassReader.java
@@ -0,0 +1,2506 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A Java class parser to make a {@link ClassVisitor} visit an existing class.
+ * This class parses a byte array conforming to the Java class file format and
+ * calls the appropriate visit methods of a given class visitor for each field,
+ * method and bytecode instruction encountered.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public class ClassReader {
+
+ /**
+ * True to enable signatures support.
+ */
+ static final boolean SIGNATURES = true;
+
+ /**
+ * True to enable annotations support.
+ */
+ static final boolean ANNOTATIONS = true;
+
+ /**
+ * True to enable stack map frames support.
+ */
+ static final boolean FRAMES = true;
+
+ /**
+ * True to enable bytecode writing support.
+ */
+ static final boolean WRITER = true;
+
+ /**
+ * True to enable JSR_W and GOTO_W support.
+ */
+ static final boolean RESIZE = true;
+
+ /**
+ * Flag to skip method code. If this class is set CODE
+ * attribute won't be visited. This can be used, for example, to retrieve
+ * annotations for methods and method parameters.
+ */
+ public static final int SKIP_CODE = 1;
+
+ /**
+ * Flag to skip the debug information in the class. If this flag is set the
+ * debug information of the class is not visited, i.e. the
+ * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
+ * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
+ * called.
+ */
+ public static final int SKIP_DEBUG = 2;
+
+ /**
+ * Flag to skip the stack map frames in the class. If this flag is set the
+ * stack map frames of the class is not visited, i.e. the
+ * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
+ * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
+ * used: it avoids visiting frames that will be ignored and recomputed from
+ * scratch in the class writer.
+ */
+ public static final int SKIP_FRAMES = 4;
+
+ /**
+ * Flag to expand the stack map frames. By default stack map frames are
+ * visited in their original format (i.e. "expanded" for classes whose
+ * version is less than V1_6, and "compressed" for the other classes). If
+ * this flag is set, stack map frames are always visited in expanded format
+ * (this option adds a decompression/recompression step in ClassReader and
+ * ClassWriter which degrades performances quite a lot).
+ */
+ public static final int EXPAND_FRAMES = 8;
+
+ /**
+ * The class to be parsed. The content of this array must not be
+ * modified. This field is intended for {@link Attribute} sub classes, and
+ * is normally not needed by class generators or adapters.
+ */
+ public final byte[] b;
+
+ /**
+ * The start index of each constant pool item in {@link #b b}, plus one. The
+ * one byte offset skips the constant pool item tag that indicates its type.
+ */
+ private final int[] items;
+
+ /**
+ * The String objects corresponding to the CONSTANT_Utf8 items. This cache
+ * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
+ * which GREATLY improves performances (by a factor 2 to 3). This caching
+ * strategy could be extended to all constant pool items, but its benefit
+ * would not be so great for these items (because they are much less
+ * expensive to parse than CONSTANT_Utf8 items).
+ */
+ private final String[] strings;
+
+ /**
+ * Maximum length of the strings contained in the constant pool of the
+ * class.
+ */
+ private final int maxStringLength;
+
+ /**
+ * Start index of the class header information (access, name...) in
+ * {@link #b b}.
+ */
+ public final int header;
+
+ // ------------------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param b
+ * the bytecode of the class to be read.
+ */
+ public ClassReader(final byte[] b) {
+ this(b, 0, b.length);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param b
+ * the bytecode of the class to be read.
+ * @param off
+ * the start offset of the class data.
+ * @param len
+ * the length of the class data.
+ */
+ public ClassReader(final byte[] b, final int off, final int len) {
+ this.b = b;
+ // checks the class version
+ if (readShort(off + 6) > Opcodes.V1_8) {
+ throw new IllegalArgumentException();
+ }
+ // parses the constant pool
+ items = new int[readUnsignedShort(off + 8)];
+ int n = items.length;
+ strings = new String[n];
+ int max = 0;
+ int index = off + 10;
+ for (int i = 1; i < n; ++i) {
+ items[i] = index + 1;
+ int size;
+ switch (b[index]) {
+ case ClassWriter.FIELD:
+ case ClassWriter.METH:
+ case ClassWriter.IMETH:
+ case ClassWriter.INT:
+ case ClassWriter.FLOAT:
+ case ClassWriter.NAME_TYPE:
+ case ClassWriter.INDY:
+ size = 5;
+ break;
+ case ClassWriter.LONG:
+ case ClassWriter.DOUBLE:
+ size = 9;
+ ++i;
+ break;
+ case ClassWriter.UTF8:
+ size = 3 + readUnsignedShort(index + 1);
+ if (size > max) {
+ max = size;
+ }
+ break;
+ case ClassWriter.HANDLE:
+ size = 4;
+ break;
+ // case ClassWriter.CLASS:
+ // case ClassWriter.STR:
+ // case ClassWriter.MTYPE
+ default:
+ size = 3;
+ break;
+ }
+ index += size;
+ }
+ maxStringLength = max;
+ // the class header information starts just after the constant pool
+ header = index;
+ }
+
+ /**
+ * Returns the class's access flags (see {@link Opcodes}). This value may
+ * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
+ * and those flags are represented by attributes.
+ *
+ * @return the class access flags
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public int getAccess() {
+ return readUnsignedShort(header);
+ }
+
+ /**
+ * Returns the internal name of the class (see
+ * {@link Type#getInternalName() getInternalName}).
+ *
+ * @return the internal class name
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getClassName() {
+ return readClass(header + 2, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal of name of the super class (see
+ * {@link Type#getInternalName() getInternalName}). For interfaces, the
+ * super class is {@link Object}.
+ *
+ * @return the internal name of super class, or null for
+ * {@link Object} class.
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getSuperName() {
+ return readClass(header + 4, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal names of the class's interfaces (see
+ * {@link Type#getInternalName() getInternalName}).
+ *
+ * @return the array of internal names for all implemented interfaces or
+ * null .
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String[] getInterfaces() {
+ int index = header + 6;
+ int n = readUnsignedShort(index);
+ String[] interfaces = new String[n];
+ if (n > 0) {
+ char[] buf = new char[maxStringLength];
+ for (int i = 0; i < n; ++i) {
+ index += 2;
+ interfaces[i] = readClass(index, buf);
+ }
+ }
+ return interfaces;
+ }
+
+ /**
+ * Copies the constant pool data into the given {@link ClassWriter}. Should
+ * be called before the {@link #accept(ClassVisitor,int)} method.
+ *
+ * @param classWriter
+ * the {@link ClassWriter} to copy constant pool into.
+ */
+ void copyPool(final ClassWriter classWriter) {
+ char[] buf = new char[maxStringLength];
+ int ll = items.length;
+ Item[] items2 = new Item[ll];
+ for (int i = 1; i < ll; i++) {
+ int index = items[i];
+ int tag = b[index - 1];
+ Item item = new Item(i);
+ int nameType;
+ switch (tag) {
+ case ClassWriter.FIELD:
+ case ClassWriter.METH:
+ case ClassWriter.IMETH:
+ nameType = items[readUnsignedShort(index + 2)];
+ item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
+ readUTF8(nameType + 2, buf));
+ break;
+ case ClassWriter.INT:
+ item.set(readInt(index));
+ break;
+ case ClassWriter.FLOAT:
+ item.set(Float.intBitsToFloat(readInt(index)));
+ break;
+ case ClassWriter.NAME_TYPE:
+ item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
+ null);
+ break;
+ case ClassWriter.LONG:
+ item.set(readLong(index));
+ ++i;
+ break;
+ case ClassWriter.DOUBLE:
+ item.set(Double.longBitsToDouble(readLong(index)));
+ ++i;
+ break;
+ case ClassWriter.UTF8: {
+ String s = strings[i];
+ if (s == null) {
+ index = items[i];
+ s = strings[i] = readUTF(index + 2,
+ readUnsignedShort(index), buf);
+ }
+ item.set(tag, s, null, null);
+ break;
+ }
+ case ClassWriter.HANDLE: {
+ int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
+ nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
+ item.set(ClassWriter.HANDLE_BASE + readByte(index),
+ readClass(fieldOrMethodRef, buf),
+ readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
+ break;
+ }
+ case ClassWriter.INDY:
+ if (classWriter.bootstrapMethods == null) {
+ copyBootstrapMethods(classWriter, items2, buf);
+ }
+ nameType = items[readUnsignedShort(index + 2)];
+ item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
+ readUnsignedShort(index));
+ break;
+ // case ClassWriter.STR:
+ // case ClassWriter.CLASS:
+ // case ClassWriter.MTYPE
+ default:
+ item.set(tag, readUTF8(index, buf), null, null);
+ break;
+ }
+
+ int index2 = item.hashCode % items2.length;
+ item.next = items2[index2];
+ items2[index2] = item;
+ }
+
+ int off = items[1] - 1;
+ classWriter.pool.putByteArray(b, off, header - off);
+ classWriter.items = items2;
+ classWriter.threshold = (int) (0.75d * ll);
+ classWriter.index = ll;
+ }
+
+ /**
+ * Copies the bootstrap method data into the given {@link ClassWriter}.
+ * Should be called before the {@link #accept(ClassVisitor,int)} method.
+ *
+ * @param classWriter
+ * the {@link ClassWriter} to copy bootstrap methods into.
+ */
+ private void copyBootstrapMethods(final ClassWriter classWriter,
+ final Item[] items, final char[] c) {
+ // finds the "BootstrapMethods" attribute
+ int u = getAttributes();
+ boolean found = false;
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ if ("BootstrapMethods".equals(attrName)) {
+ found = true;
+ break;
+ }
+ u += 6 + readInt(u + 4);
+ }
+ if (!found) {
+ return;
+ }
+ // copies the bootstrap methods in the class writer
+ int boostrapMethodCount = readUnsignedShort(u + 8);
+ for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
+ int position = v - u - 10;
+ int hashCode = readConst(readUnsignedShort(v), c).hashCode();
+ for (int k = readUnsignedShort(v + 2); k > 0; --k) {
+ hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
+ v += 2;
+ }
+ v += 4;
+ Item item = new Item(j);
+ item.set(position, hashCode & 0x7FFFFFFF);
+ int index = item.hashCode % items.length;
+ item.next = items[index];
+ items[index] = item;
+ }
+ int attrSize = readInt(u + 4);
+ ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
+ bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
+ classWriter.bootstrapMethodsCount = boostrapMethodCount;
+ classWriter.bootstrapMethods = bootstrapMethods;
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param is
+ * an input stream from which to read the class.
+ * @throws IOException
+ * if a problem occurs during reading.
+ */
+ public ClassReader(final InputStream is) throws IOException {
+ this(readClass(is, false));
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param name
+ * the binary qualified name of the class to be read.
+ * @throws IOException
+ * if an exception occurs during reading.
+ */
+ public ClassReader(final String name) throws IOException {
+ this(readClass(
+ ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
+ + ".class"), true));
+ }
+
+ /**
+ * Reads the bytecode of a class.
+ *
+ * @param is
+ * an input stream from which to read the class.
+ * @param close
+ * true to close the input stream after reading.
+ * @return the bytecode read from the given input stream.
+ * @throws IOException
+ * if a problem occurs during reading.
+ */
+ private static byte[] readClass(final InputStream is, boolean close)
+ throws IOException {
+ if (is == null) {
+ throw new IOException("Class not found");
+ }
+ try {
+ byte[] b = new byte[is.available()];
+ int len = 0;
+ while (true) {
+ int n = is.read(b, len, b.length - len);
+ if (n == -1) {
+ if (len < b.length) {
+ byte[] c = new byte[len];
+ System.arraycopy(b, 0, c, 0, len);
+ b = c;
+ }
+ return b;
+ }
+ len += n;
+ if (len == b.length) {
+ int last = is.read();
+ if (last < 0) {
+ return b;
+ }
+ byte[] c = new byte[b.length + 1000];
+ System.arraycopy(b, 0, c, 0, len);
+ c[len++] = (byte) last;
+ b = c;
+ }
+ }
+ } finally {
+ if (close) {
+ is.close();
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Public methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Makes the given visitor visit the Java class of this {@link ClassReader}
+ * . This class is the one specified in the constructor (see
+ * {@link #ClassReader(byte[]) ClassReader}).
+ *
+ * @param classVisitor
+ * the visitor that must visit this class.
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
+ * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
+ */
+ public void accept(final ClassVisitor classVisitor, final int flags) {
+ accept(classVisitor, new Attribute[0], flags);
+ }
+
+ /**
+ * Makes the given visitor visit the Java class of this {@link ClassReader}.
+ * This class is the one specified in the constructor (see
+ * {@link #ClassReader(byte[]) ClassReader}).
+ *
+ * @param classVisitor
+ * the visitor that must visit this class.
+ * @param attrs
+ * prototypes of the attributes that must be parsed during the
+ * visit of the class. Any attribute whose type is not equal to
+ * the type of one the prototypes will not be parsed: its byte
+ * array value will be passed unchanged to the ClassWriter.
+ * This may corrupt it if this value contains references to
+ * the constant pool, or has syntactic or semantic links with a
+ * class element that has been transformed by a class adapter
+ * between the reader and the writer .
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
+ * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
+ */
+ public void accept(final ClassVisitor classVisitor,
+ final Attribute[] attrs, final int flags) {
+ int u = header; // current offset in the class file
+ char[] c = new char[maxStringLength]; // buffer used to read strings
+
+ Context context = new Context();
+ context.attrs = attrs;
+ context.flags = flags;
+ context.buffer = c;
+
+ // reads the class declaration
+ int access = readUnsignedShort(u);
+ String name = readClass(u + 2, c);
+ String superClass = readClass(u + 4, c);
+ String[] interfaces = new String[readUnsignedShort(u + 6)];
+ u += 8;
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = readClass(u, c);
+ u += 2;
+ }
+
+ // reads the class attributes
+ String signature = null;
+ String sourceFile = null;
+ String sourceDebug = null;
+ String enclosingOwner = null;
+ String enclosingName = null;
+ String enclosingDesc = null;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ int innerClasses = 0;
+ Attribute attributes = null;
+
+ u = getAttributes();
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("SourceFile".equals(attrName)) {
+ sourceFile = readUTF8(u + 8, c);
+ } else if ("InnerClasses".equals(attrName)) {
+ innerClasses = u + 8;
+ } else if ("EnclosingMethod".equals(attrName)) {
+ enclosingOwner = readClass(u + 8, c);
+ int item = readUnsignedShort(u + 10);
+ if (item != 0) {
+ enclosingName = readUTF8(items[item], c);
+ enclosingDesc = readUTF8(items[item] + 2, c);
+ }
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if ("Deprecated".equals(attrName)) {
+ access |= Opcodes.ACC_DEPRECATED;
+ } else if ("Synthetic".equals(attrName)) {
+ access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if ("SourceDebugExtension".equals(attrName)) {
+ int len = readInt(u + 4);
+ sourceDebug = readUTF(u + 8, len, new char[len]);
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else if ("BootstrapMethods".equals(attrName)) {
+ int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
+ for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
+ bootstrapMethods[j] = v;
+ v += 2 + readUnsignedShort(v + 2) << 1;
+ }
+ context.bootstrapMethods = bootstrapMethods;
+ } else {
+ Attribute attr = readAttribute(attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+
+ // visits the class declaration
+ classVisitor.visit(readInt(items[1] - 7), access, name, signature,
+ superClass, interfaces);
+
+ // visits the source and debug info
+ if ((flags & SKIP_DEBUG) == 0
+ && (sourceFile != null || sourceDebug != null)) {
+ classVisitor.visitSource(sourceFile, sourceDebug);
+ }
+
+ // visits the outer class
+ if (enclosingOwner != null) {
+ classVisitor.visitOuterClass(enclosingOwner, enclosingName,
+ enclosingDesc);
+ }
+
+ // visits the class annotations and type annotations
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+
+ // visits the attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ classVisitor.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the inner classes
+ if (innerClasses != 0) {
+ int v = innerClasses + 2;
+ for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
+ classVisitor.visitInnerClass(readClass(v, c),
+ readClass(v + 2, c), readUTF8(v + 4, c),
+ readUnsignedShort(v + 6));
+ v += 8;
+ }
+ }
+
+ // visits the fields and methods
+ u = header + 10 + 2 * interfaces.length;
+ for (int i = readUnsignedShort(u - 2); i > 0; --i) {
+ u = readField(classVisitor, context, u);
+ }
+ u += 2;
+ for (int i = readUnsignedShort(u - 2); i > 0; --i) {
+ u = readMethod(classVisitor, context, u);
+ }
+
+ // visits the end of the class
+ classVisitor.visitEnd();
+ }
+
+ /**
+ * Reads a field and makes the given visitor visit it.
+ *
+ * @param classVisitor
+ * the visitor that must visit the field.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the field in the class file.
+ * @return the offset of the first byte following the field in the class.
+ */
+ private int readField(final ClassVisitor classVisitor,
+ final Context context, int u) {
+ // reads the field declaration
+ char[] c = context.buffer;
+ int access = readUnsignedShort(u);
+ String name = readUTF8(u + 2, c);
+ String desc = readUTF8(u + 4, c);
+ u += 6;
+
+ // reads the field attributes
+ String signature = null;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ Object value = null;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("ConstantValue".equals(attrName)) {
+ int item = readUnsignedShort(u + 8);
+ value = item == 0 ? null : readConst(item, c);
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if ("Deprecated".equals(attrName)) {
+ access |= Opcodes.ACC_DEPRECATED;
+ } else if ("Synthetic".equals(attrName)) {
+ access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else {
+ Attribute attr = readAttribute(context.attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // visits the field declaration
+ FieldVisitor fv = classVisitor.visitField(access, name, desc,
+ signature, value);
+ if (fv == null) {
+ return u;
+ }
+
+ // visits the field annotations and type annotations
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+
+ // visits the field attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ fv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the end of the field
+ fv.visitEnd();
+
+ return u;
+ }
+
+ /**
+ * Reads a method and makes the given visitor visit it.
+ *
+ * @param classVisitor
+ * the visitor that must visit the method.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the method in the class file.
+ * @return the offset of the first byte following the method in the class.
+ */
+ private int readMethod(final ClassVisitor classVisitor,
+ final Context context, int u) {
+ // reads the method declaration
+ char[] c = context.buffer;
+ context.access = readUnsignedShort(u);
+ context.name = readUTF8(u + 2, c);
+ context.desc = readUTF8(u + 4, c);
+ u += 6;
+
+ // reads the method attributes
+ int code = 0;
+ int exception = 0;
+ String[] exceptions = null;
+ String signature = null;
+ int methodParameters = 0;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ int dann = 0;
+ int mpanns = 0;
+ int impanns = 0;
+ int firstAttribute = u;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("Code".equals(attrName)) {
+ if ((context.flags & SKIP_CODE) == 0) {
+ code = u + 8;
+ }
+ } else if ("Exceptions".equals(attrName)) {
+ exceptions = new String[readUnsignedShort(u + 8)];
+ exception = u + 10;
+ for (int j = 0; j < exceptions.length; ++j) {
+ exceptions[j] = readClass(exception, c);
+ exception += 2;
+ }
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if ("Deprecated".equals(attrName)) {
+ context.access |= Opcodes.ACC_DEPRECATED;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
+ dann = u + 8;
+ } else if ("Synthetic".equals(attrName)) {
+ context.access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleParameterAnnotations".equals(attrName)) {
+ mpanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
+ impanns = u + 8;
+ } else if ("MethodParameters".equals(attrName)) {
+ methodParameters = u + 8;
+ } else {
+ Attribute attr = readAttribute(context.attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // visits the method declaration
+ MethodVisitor mv = classVisitor.visitMethod(context.access,
+ context.name, context.desc, signature, exceptions);
+ if (mv == null) {
+ return u;
+ }
+
+ /*
+ * if the returned MethodVisitor is in fact a MethodWriter, it means
+ * there is no method adapter between the reader and the writer. If, in
+ * addition, the writer's constant pool was copied from this reader
+ * (mw.cw.cr == this), and the signature and exceptions of the method
+ * have not been changed, then it is possible to skip all visit events
+ * and just copy the original code of the method to the writer (the
+ * access, name and descriptor can have been changed, this is not
+ * important since they are not copied as is from the reader).
+ */
+ if (WRITER && mv instanceof MethodWriter) {
+ MethodWriter mw = (MethodWriter) mv;
+ if (mw.cw.cr == this && signature == mw.signature) {
+ boolean sameExceptions = false;
+ if (exceptions == null) {
+ sameExceptions = mw.exceptionCount == 0;
+ } else if (exceptions.length == mw.exceptionCount) {
+ sameExceptions = true;
+ for (int j = exceptions.length - 1; j >= 0; --j) {
+ exception -= 2;
+ if (mw.exceptions[j] != readUnsignedShort(exception)) {
+ sameExceptions = false;
+ break;
+ }
+ }
+ }
+ if (sameExceptions) {
+ /*
+ * we do not copy directly the code into MethodWriter to
+ * save a byte array copy operation. The real copy will be
+ * done in ClassWriter.toByteArray().
+ */
+ mw.classReaderOffset = firstAttribute;
+ mw.classReaderLength = u - firstAttribute;
+ return u;
+ }
+ }
+ }
+
+ // visit the method parameters
+ if (methodParameters != 0) {
+ for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
+ mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
+ }
+ }
+
+ // visits the method annotations
+ if (ANNOTATIONS && dann != 0) {
+ AnnotationVisitor dv = mv.visitAnnotationDefault();
+ readAnnotationValue(dann, c, null, dv);
+ if (dv != null) {
+ dv.visitEnd();
+ }
+ }
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && mpanns != 0) {
+ readParameterAnnotations(mv, context, mpanns, true);
+ }
+ if (ANNOTATIONS && impanns != 0) {
+ readParameterAnnotations(mv, context, impanns, false);
+ }
+
+ // visits the method attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ mv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the method code
+ if (code != 0) {
+ mv.visitCode();
+ readCode(mv, context, code);
+ }
+
+ // visits the end of the method
+ mv.visitEnd();
+
+ return u;
+ }
+
+ /**
+ * Reads the bytecode of a method and makes the given visitor visit it.
+ *
+ * @param mv
+ * the visitor that must visit the method's code.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the code attribute in the class file.
+ */
+ private void readCode(final MethodVisitor mv, final Context context, int u) {
+ // reads the header
+ byte[] b = this.b;
+ char[] c = context.buffer;
+ int maxStack = readUnsignedShort(u);
+ int maxLocals = readUnsignedShort(u + 2);
+ int codeLength = readInt(u + 4);
+ u += 8;
+
+ // reads the bytecode to find the labels
+ int codeStart = u;
+ int codeEnd = u + codeLength;
+ Label[] labels = context.labels = new Label[codeLength + 2];
+ readLabel(codeLength + 1, labels);
+ while (u < codeEnd) {
+ int offset = u - codeStart;
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ readLabel(offset + readShort(u + 1), labels);
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ readLabel(offset + readInt(u + 1), labels);
+ u += 5;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ u += 6;
+ } else {
+ u += 4;
+ }
+ break;
+ case ClassWriter.TABL_INSN:
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ readLabel(offset + readInt(u), labels);
+ for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
+ readLabel(offset + readInt(u + 12), labels);
+ u += 4;
+ }
+ u += 12;
+ break;
+ case ClassWriter.LOOK_INSN:
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ readLabel(offset + readInt(u), labels);
+ for (int i = readInt(u + 4); i > 0; --i) {
+ readLabel(offset + readInt(u + 12), labels);
+ u += 8;
+ }
+ u += 8;
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ u += 5;
+ break;
+ // case MANA_INSN:
+ default:
+ u += 4;
+ break;
+ }
+ }
+
+ // reads the try catch entries to find the labels, and also visits them
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ Label start = readLabel(readUnsignedShort(u + 2), labels);
+ Label end = readLabel(readUnsignedShort(u + 4), labels);
+ Label handler = readLabel(readUnsignedShort(u + 6), labels);
+ String type = readUTF8(items[readUnsignedShort(u + 8)], c);
+ mv.visitTryCatchBlock(start, end, handler, type);
+ u += 8;
+ }
+ u += 2;
+
+ // reads the code attributes
+ int[] tanns = null; // start index of each visible type annotation
+ int[] itanns = null; // start index of each invisible type annotation
+ int tann = 0; // current index in tanns array
+ int itann = 0; // current index in itanns array
+ int ntoff = -1; // next visible type annotation code offset
+ int nitoff = -1; // next invisible type annotation code offset
+ int varTable = 0;
+ int varTypeTable = 0;
+ boolean zip = true;
+ boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
+ int stackMap = 0;
+ int stackMapSize = 0;
+ int frameCount = 0;
+ Context frame = null;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ if ("LocalVariableTable".equals(attrName)) {
+ if ((context.flags & SKIP_DEBUG) == 0) {
+ varTable = u + 8;
+ for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
+ int label = readUnsignedShort(v + 10);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ label += readUnsignedShort(v + 12);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ v += 10;
+ }
+ }
+ } else if ("LocalVariableTypeTable".equals(attrName)) {
+ varTypeTable = u + 8;
+ } else if ("LineNumberTable".equals(attrName)) {
+ if ((context.flags & SKIP_DEBUG) == 0) {
+ for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
+ int label = readUnsignedShort(v + 10);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ Label l = labels[label];
+ while (l.line > 0) {
+ if (l.next == null) {
+ l.next = new Label();
+ }
+ l = l.next;
+ }
+ l.line = readUnsignedShort(v + 12);
+ v += 4;
+ }
+ }
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = readTypeAnnotations(mv, context, u + 8, true);
+ ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1
+ : readUnsignedShort(tanns[0] + 1);
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = readTypeAnnotations(mv, context, u + 8, false);
+ nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1
+ : readUnsignedShort(itanns[0] + 1);
+ } else if (FRAMES && "StackMapTable".equals(attrName)) {
+ if ((context.flags & SKIP_FRAMES) == 0) {
+ stackMap = u + 10;
+ stackMapSize = readInt(u + 4);
+ frameCount = readUnsignedShort(u + 8);
+ }
+ /*
+ * here we do not extract the labels corresponding to the
+ * attribute content. This would require a full parsing of the
+ * attribute, which would need to be repeated in the second
+ * phase (see below). Instead the content of the attribute is
+ * read one frame at a time (i.e. after a frame has been
+ * visited, the next frame is read), and the labels it contains
+ * are also extracted one frame at a time. Thanks to the
+ * ordering of frames, having only a "one frame lookahead" is
+ * not a problem, i.e. it is not possible to see an offset
+ * smaller than the offset of the current insn and for which no
+ * Label exist.
+ */
+ /*
+ * This is not true for UNINITIALIZED type offsets. We solve
+ * this by parsing the stack map table without a full decoding
+ * (see below).
+ */
+ } else if (FRAMES && "StackMap".equals(attrName)) {
+ if ((context.flags & SKIP_FRAMES) == 0) {
+ zip = false;
+ stackMap = u + 10;
+ stackMapSize = readInt(u + 4);
+ frameCount = readUnsignedShort(u + 8);
+ }
+ /*
+ * IMPORTANT! here we assume that the frames are ordered, as in
+ * the StackMapTable attribute, although this is not guaranteed
+ * by the attribute format.
+ */
+ } else {
+ for (int j = 0; j < context.attrs.length; ++j) {
+ if (context.attrs[j].type.equals(attrName)) {
+ Attribute attr = context.attrs[j].read(this, u + 8,
+ readInt(u + 4), c, codeStart - 8, labels);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // generates the first (implicit) stack map frame
+ if (FRAMES && stackMap != 0) {
+ /*
+ * for the first explicit frame the offset is not offset_delta + 1
+ * but only offset_delta; setting the implicit frame offset to -1
+ * allow the use of the "offset_delta + 1" rule in all cases
+ */
+ frame = context;
+ frame.offset = -1;
+ frame.mode = 0;
+ frame.localCount = 0;
+ frame.localDiff = 0;
+ frame.stackCount = 0;
+ frame.local = new Object[maxLocals];
+ frame.stack = new Object[maxStack];
+ if (unzip) {
+ getImplicitFrame(context);
+ }
+ /*
+ * Finds labels for UNINITIALIZED frame types. Instead of decoding
+ * each element of the stack map table, we look for 3 consecutive
+ * bytes that "look like" an UNINITIALIZED type (tag 8, offset
+ * within code bounds, NEW instruction at this offset). We may find
+ * false positives (i.e. not real UNINITIALIZED types), but this
+ * should be rare, and the only consequence will be the creation of
+ * an unneeded label. This is better than creating a label for each
+ * NEW instruction, and faster than fully decoding the whole stack
+ * map table.
+ */
+ for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
+ if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
+ int v = readUnsignedShort(i + 1);
+ if (v >= 0 && v < codeLength) {
+ if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
+ readLabel(v, labels);
+ }
+ }
+ }
+ }
+ }
+
+ // visits the instructions
+ u = codeStart;
+ while (u < codeEnd) {
+ int offset = u - codeStart;
+
+ // visits the label and line number for this offset, if any
+ Label l = labels[offset];
+ if (l != null) {
+ Label next = l.next;
+ l.next = null;
+ mv.visitLabel(l);
+ if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
+ mv.visitLineNumber(l.line, l);
+ while (next != null) {
+ mv.visitLineNumber(next.line, l);
+ next = next.next;
+ }
+ }
+ }
+
+ // visits the frame for this offset, if any
+ while (FRAMES && frame != null
+ && (frame.offset == offset || frame.offset == -1)) {
+ // if there is a frame for this offset, makes the visitor visit
+ // it, and reads the next frame if there is one.
+ if (frame.offset != -1) {
+ if (!zip || unzip) {
+ mv.visitFrame(Opcodes.F_NEW, frame.localCount,
+ frame.local, frame.stackCount, frame.stack);
+ } else {
+ mv.visitFrame(frame.mode, frame.localDiff, frame.local,
+ frame.stackCount, frame.stack);
+ }
+ }
+ if (frameCount > 0) {
+ stackMap = readFrame(stackMap, zip, unzip, frame);
+ --frameCount;
+ } else {
+ frame = null;
+ }
+ }
+
+ // visits the instruction at this offset
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ mv.visitInsn(opcode);
+ u += 1;
+ break;
+ case ClassWriter.IMPLVAR_INSN:
+ if (opcode > Opcodes.ISTORE) {
+ opcode -= 59; // ISTORE_0
+ mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
+ opcode & 0x3);
+ } else {
+ opcode -= 26; // ILOAD_0
+ mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
+ }
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]);
+ u += 5;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
+ u += 6;
+ } else {
+ mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
+ u += 4;
+ }
+ break;
+ case ClassWriter.TABL_INSN: {
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ int label = offset + readInt(u);
+ int min = readInt(u + 4);
+ int max = readInt(u + 8);
+ Label[] table = new Label[max - min + 1];
+ u += 12;
+ for (int i = 0; i < table.length; ++i) {
+ table[i] = labels[offset + readInt(u)];
+ u += 4;
+ }
+ mv.visitTableSwitchInsn(min, max, labels[label], table);
+ break;
+ }
+ case ClassWriter.LOOK_INSN: {
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ int label = offset + readInt(u);
+ int len = readInt(u + 4);
+ int[] keys = new int[len];
+ Label[] values = new Label[len];
+ u += 8;
+ for (int i = 0; i < len; ++i) {
+ keys[i] = readInt(u);
+ values[i] = labels[offset + readInt(u + 4)];
+ u += 8;
+ }
+ mv.visitLookupSwitchInsn(labels[label], keys, values);
+ break;
+ }
+ case ClassWriter.VAR_INSN:
+ mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
+ u += 2;
+ break;
+ case ClassWriter.SBYTE_INSN:
+ mv.visitIntInsn(opcode, b[u + 1]);
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ mv.visitIntInsn(opcode, readShort(u + 1));
+ u += 3;
+ break;
+ case ClassWriter.LDC_INSN:
+ mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
+ u += 2;
+ break;
+ case ClassWriter.LDCW_INSN:
+ mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
+ u += 3;
+ break;
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.ITFMETH_INSN: {
+ int cpIndex = items[readUnsignedShort(u + 1)];
+ boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
+ String iowner = readClass(cpIndex, c);
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String iname = readUTF8(cpIndex, c);
+ String idesc = readUTF8(cpIndex + 2, c);
+ if (opcode < Opcodes.INVOKEVIRTUAL) {
+ mv.visitFieldInsn(opcode, iowner, iname, idesc);
+ } else {
+ mv.visitMethodInsn(opcode, iowner, iname, idesc, itf);
+ }
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ u += 5;
+ } else {
+ u += 3;
+ }
+ break;
+ }
+ case ClassWriter.INDYMETH_INSN: {
+ int cpIndex = items[readUnsignedShort(u + 1)];
+ int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
+ Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
+ int bsmArgCount = readUnsignedShort(bsmIndex + 2);
+ Object[] bsmArgs = new Object[bsmArgCount];
+ bsmIndex += 4;
+ for (int i = 0; i < bsmArgCount; i++) {
+ bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
+ bsmIndex += 2;
+ }
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String iname = readUTF8(cpIndex, c);
+ String idesc = readUTF8(cpIndex + 2, c);
+ mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
+ u += 5;
+ break;
+ }
+ case ClassWriter.TYPE_INSN:
+ mv.visitTypeInsn(opcode, readClass(u + 1, c));
+ u += 3;
+ break;
+ case ClassWriter.IINC_INSN:
+ mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
+ u += 3;
+ break;
+ // case MANA_INSN:
+ default:
+ mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
+ u += 4;
+ break;
+ }
+
+ // visit the instruction annotations, if any
+ while (tanns != null && tann < tanns.length && ntoff <= offset) {
+ if (ntoff == offset) {
+ int v = readAnnotationTarget(context, tanns[tann]);
+ readAnnotationValues(v + 2, c, true,
+ mv.visitInsnAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1
+ : readUnsignedShort(tanns[tann] + 1);
+ }
+ while (itanns != null && itann < itanns.length && nitoff <= offset) {
+ if (nitoff == offset) {
+ int v = readAnnotationTarget(context, itanns[itann]);
+ readAnnotationValues(v + 2, c, true,
+ mv.visitInsnAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ nitoff = ++itann >= itanns.length
+ || readByte(itanns[itann]) < 0x43 ? -1
+ : readUnsignedShort(itanns[itann] + 1);
+ }
+ }
+ if (labels[codeLength] != null) {
+ mv.visitLabel(labels[codeLength]);
+ }
+
+ // visits the local variable tables
+ if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
+ int[] typeTable = null;
+ if (varTypeTable != 0) {
+ u = varTypeTable + 2;
+ typeTable = new int[readUnsignedShort(varTypeTable) * 3];
+ for (int i = typeTable.length; i > 0;) {
+ typeTable[--i] = u + 6; // signature
+ typeTable[--i] = readUnsignedShort(u + 8); // index
+ typeTable[--i] = readUnsignedShort(u); // start
+ u += 10;
+ }
+ }
+ u = varTable + 2;
+ for (int i = readUnsignedShort(varTable); i > 0; --i) {
+ int start = readUnsignedShort(u);
+ int length = readUnsignedShort(u + 2);
+ int index = readUnsignedShort(u + 8);
+ String vsignature = null;
+ if (typeTable != null) {
+ for (int j = 0; j < typeTable.length; j += 3) {
+ if (typeTable[j] == start && typeTable[j + 1] == index) {
+ vsignature = readUTF8(typeTable[j + 2], c);
+ break;
+ }
+ }
+ }
+ mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
+ vsignature, labels[start], labels[start + length],
+ index);
+ u += 10;
+ }
+ }
+
+ // visits the local variables type annotations
+ if (tanns != null) {
+ for (int i = 0; i < tanns.length; ++i) {
+ if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) {
+ int v = readAnnotationTarget(context, tanns[i]);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitLocalVariableAnnotation(context.typeRef,
+ context.typePath, context.start,
+ context.end, context.index, readUTF8(v, c),
+ true));
+ }
+ }
+ }
+ if (itanns != null) {
+ for (int i = 0; i < itanns.length; ++i) {
+ if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) {
+ int v = readAnnotationTarget(context, itanns[i]);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitLocalVariableAnnotation(context.typeRef,
+ context.typePath, context.start,
+ context.end, context.index, readUTF8(v, c),
+ false));
+ }
+ }
+ }
+
+ // visits the code attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ mv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the max stack and max locals values
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * Parses a type annotation table to find the labels, and to visit the try
+ * catch block annotations.
+ *
+ * @param u
+ * the start offset of a type annotation table.
+ * @param mv
+ * the method visitor to be used to visit the try catch block
+ * annotations.
+ * @param context
+ * information about the class being parsed.
+ * @param visible
+ * if the type annotation table to parse contains runtime visible
+ * annotations.
+ * @return the start offset of each type annotation in the parsed table.
+ */
+ private int[] readTypeAnnotations(final MethodVisitor mv,
+ final Context context, int u, boolean visible) {
+ char[] c = context.buffer;
+ int[] offsets = new int[readUnsignedShort(u)];
+ u += 2;
+ for (int i = 0; i < offsets.length; ++i) {
+ offsets[i] = u;
+ int target = readInt(u);
+ switch (target >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ u += 2;
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ u += 1;
+ break;
+ case 0x40: // LOCAL_VARIABLE
+ case 0x41: // RESOURCE_VARIABLE
+ for (int j = readUnsignedShort(u + 1); j > 0; --j) {
+ int start = readUnsignedShort(u + 3);
+ int length = readUnsignedShort(u + 5);
+ readLabel(start, context.labels);
+ readLabel(start + length, context.labels);
+ u += 6;
+ }
+ u += 3;
+ break;
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ u += 4;
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ u += 3;
+ break;
+ }
+ int pathLength = readByte(u);
+ if ((target >>> 24) == 0x42) {
+ TypePath path = pathLength == 0 ? null : new TypePath(b, u);
+ u += 1 + 2 * pathLength;
+ u = readAnnotationValues(u + 2, c, true,
+ mv.visitTryCatchAnnotation(target, path,
+ readUTF8(u, c), visible));
+ } else {
+ u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null);
+ }
+ }
+ return offsets;
+ }
+
+ /**
+ * Parses the header of a type annotation to extract its target_type and
+ * target_path (the result is stored in the given context), and returns the
+ * start offset of the rest of the type_annotation structure (i.e. the
+ * offset to the type_index field, which is followed by
+ * num_element_value_pairs and then the name,value pairs).
+ *
+ * @param context
+ * information about the class being parsed. This is where the
+ * extracted target_type and target_path must be stored.
+ * @param u
+ * the start offset of a type_annotation structure.
+ * @return the start offset of the rest of the type_annotation structure.
+ */
+ private int readAnnotationTarget(final Context context, int u) {
+ int target = readInt(u);
+ switch (target >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ target &= 0xFFFF0000;
+ u += 2;
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ target &= 0xFF000000;
+ u += 1;
+ break;
+ case 0x40: // LOCAL_VARIABLE
+ case 0x41: { // RESOURCE_VARIABLE
+ target &= 0xFF000000;
+ int n = readUnsignedShort(u + 1);
+ context.start = new Label[n];
+ context.end = new Label[n];
+ context.index = new int[n];
+ u += 3;
+ for (int i = 0; i < n; ++i) {
+ int start = readUnsignedShort(u);
+ int length = readUnsignedShort(u + 2);
+ context.start[i] = readLabel(start, context.labels);
+ context.end[i] = readLabel(start + length, context.labels);
+ context.index[i] = readUnsignedShort(u + 4);
+ u += 6;
+ }
+ break;
+ }
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ target &= 0xFF0000FF;
+ u += 4;
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000;
+ u += 3;
+ break;
+ }
+ int pathLength = readByte(u);
+ context.typeRef = target;
+ context.typePath = pathLength == 0 ? null : new TypePath(b, u);
+ return u + 1 + 2 * pathLength;
+ }
+
+ /**
+ * Reads parameter annotations and makes the given visitor visit them.
+ *
+ * @param mv
+ * the visitor that must visit the annotations.
+ * @param context
+ * information about the class being parsed.
+ * @param v
+ * start offset in {@link #b b} of the annotations to be read.
+ * @param visible
+ * true if the annotations to be read are visible at
+ * runtime.
+ */
+ private void readParameterAnnotations(final MethodVisitor mv,
+ final Context context, int v, final boolean visible) {
+ int i;
+ int n = b[v++] & 0xFF;
+ // workaround for a bug in javac (javac compiler generates a parameter
+ // annotation array whose size is equal to the number of parameters in
+ // the Java source file, while it should generate an array whose size is
+ // equal to the number of parameters in the method descriptor - which
+ // includes the synthetic parameters added by the compiler). This work-
+ // around supposes that the synthetic parameters are the first ones.
+ int synthetics = Type.getArgumentTypes(context.desc).length - n;
+ AnnotationVisitor av;
+ for (i = 0; i < synthetics; ++i) {
+ // virtual annotation to detect synthetic parameters in MethodWriter
+ av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
+ if (av != null) {
+ av.visitEnd();
+ }
+ }
+ char[] c = context.buffer;
+ for (; i < n + synthetics; ++i) {
+ int j = readUnsignedShort(v);
+ v += 2;
+ for (; j > 0; --j) {
+ av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible);
+ v = readAnnotationValues(v + 2, c, true, av);
+ }
+ }
+ }
+
+ /**
+ * Reads the values of an annotation and makes the given visitor visit them.
+ *
+ * @param v
+ * the start offset in {@link #b b} of the values to be read
+ * (including the unsigned short that gives the number of
+ * values).
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param named
+ * if the annotation values are named or not.
+ * @param av
+ * the visitor that must visit the values.
+ * @return the end offset of the annotation values.
+ */
+ private int readAnnotationValues(int v, final char[] buf,
+ final boolean named, final AnnotationVisitor av) {
+ int i = readUnsignedShort(v);
+ v += 2;
+ if (named) {
+ for (; i > 0; --i) {
+ v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
+ }
+ } else {
+ for (; i > 0; --i) {
+ v = readAnnotationValue(v, buf, null, av);
+ }
+ }
+ if (av != null) {
+ av.visitEnd();
+ }
+ return v;
+ }
+
+ /**
+ * Reads a value of an annotation and makes the given visitor visit it.
+ *
+ * @param v
+ * the start offset in {@link #b b} of the value to be read
+ * (not including the value name constant pool index ).
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param name
+ * the name of the value to be read.
+ * @param av
+ * the visitor that must visit the value.
+ * @return the end offset of the annotation value.
+ */
+ private int readAnnotationValue(int v, final char[] buf, final String name,
+ final AnnotationVisitor av) {
+ int i;
+ if (av == null) {
+ switch (b[v] & 0xFF) {
+ case 'e': // enum_const_value
+ return v + 5;
+ case '@': // annotation_value
+ return readAnnotationValues(v + 3, buf, true, null);
+ case '[': // array_value
+ return readAnnotationValues(v + 1, buf, false, null);
+ default:
+ return v + 3;
+ }
+ }
+ switch (b[v++] & 0xFF) {
+ case 'I': // pointer to CONSTANT_Integer
+ case 'J': // pointer to CONSTANT_Long
+ case 'F': // pointer to CONSTANT_Float
+ case 'D': // pointer to CONSTANT_Double
+ av.visit(name, readConst(readUnsignedShort(v), buf));
+ v += 2;
+ break;
+ case 'B': // pointer to CONSTANT_Byte
+ av.visit(name, (byte) readInt(items[readUnsignedShort(v)]));
+ v += 2;
+ break;
+ case 'Z': // pointer to CONSTANT_Boolean
+ av.visit(name,
+ readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
+ : Boolean.TRUE);
+ v += 2;
+ break;
+ case 'S': // pointer to CONSTANT_Short
+ av.visit(name, (short) readInt(items[readUnsignedShort(v)]));
+ v += 2;
+ break;
+ case 'C': // pointer to CONSTANT_Char
+ av.visit(name, (char) readInt(items[readUnsignedShort(v)]));
+ v += 2;
+ break;
+ case 's': // pointer to CONSTANT_Utf8
+ av.visit(name, readUTF8(v, buf));
+ v += 2;
+ break;
+ case 'e': // enum_const_value
+ av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
+ v += 4;
+ break;
+ case 'c': // class_info
+ av.visit(name, Type.getType(readUTF8(v, buf)));
+ v += 2;
+ break;
+ case '@': // annotation_value
+ v = readAnnotationValues(v + 2, buf, true,
+ av.visitAnnotation(name, readUTF8(v, buf)));
+ break;
+ case '[': // array_value
+ int size = readUnsignedShort(v);
+ v += 2;
+ if (size == 0) {
+ return readAnnotationValues(v - 2, buf, false,
+ av.visitArray(name));
+ }
+ switch (this.b[v++] & 0xFF) {
+ case 'B':
+ byte[] bv = new byte[size];
+ for (i = 0; i < size; i++) {
+ bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, bv);
+ --v;
+ break;
+ case 'Z':
+ boolean[] zv = new boolean[size];
+ for (i = 0; i < size; i++) {
+ zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
+ v += 3;
+ }
+ av.visit(name, zv);
+ --v;
+ break;
+ case 'S':
+ short[] sv = new short[size];
+ for (i = 0; i < size; i++) {
+ sv[i] = (short) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, sv);
+ --v;
+ break;
+ case 'C':
+ char[] cv = new char[size];
+ for (i = 0; i < size; i++) {
+ cv[i] = (char) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, cv);
+ --v;
+ break;
+ case 'I':
+ int[] iv = new int[size];
+ for (i = 0; i < size; i++) {
+ iv[i] = readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, iv);
+ --v;
+ break;
+ case 'J':
+ long[] lv = new long[size];
+ for (i = 0; i < size; i++) {
+ lv[i] = readLong(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, lv);
+ --v;
+ break;
+ case 'F':
+ float[] fv = new float[size];
+ for (i = 0; i < size; i++) {
+ fv[i] = Float
+ .intBitsToFloat(readInt(items[readUnsignedShort(v)]));
+ v += 3;
+ }
+ av.visit(name, fv);
+ --v;
+ break;
+ case 'D':
+ double[] dv = new double[size];
+ for (i = 0; i < size; i++) {
+ dv[i] = Double
+ .longBitsToDouble(readLong(items[readUnsignedShort(v)]));
+ v += 3;
+ }
+ av.visit(name, dv);
+ --v;
+ break;
+ default:
+ v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
+ }
+ }
+ return v;
+ }
+
+ /**
+ * Computes the implicit frame of the method currently being parsed (as
+ * defined in the given {@link Context}) and stores it in the given context.
+ *
+ * @param frame
+ * information about the class being parsed.
+ */
+ private void getImplicitFrame(final Context frame) {
+ String desc = frame.desc;
+ Object[] locals = frame.local;
+ int local = 0;
+ if ((frame.access & Opcodes.ACC_STATIC) == 0) {
+ if ("".equals(frame.name)) {
+ locals[local++] = Opcodes.UNINITIALIZED_THIS;
+ } else {
+ locals[local++] = readClass(header + 2, frame.buffer);
+ }
+ }
+ int i = 1;
+ loop: while (true) {
+ int j = i;
+ switch (desc.charAt(i++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ locals[local++] = Opcodes.INTEGER;
+ break;
+ case 'F':
+ locals[local++] = Opcodes.FLOAT;
+ break;
+ case 'J':
+ locals[local++] = Opcodes.LONG;
+ break;
+ case 'D':
+ locals[local++] = Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (desc.charAt(i) == '[') {
+ ++i;
+ }
+ if (desc.charAt(i) == 'L') {
+ ++i;
+ while (desc.charAt(i) != ';') {
+ ++i;
+ }
+ }
+ locals[local++] = desc.substring(j, ++i);
+ break;
+ case 'L':
+ while (desc.charAt(i) != ';') {
+ ++i;
+ }
+ locals[local++] = desc.substring(j + 1, i++);
+ break;
+ default:
+ break loop;
+ }
+ }
+ frame.localCount = local;
+ }
+
+ /**
+ * Reads a stack map frame and stores the result in the given
+ * {@link Context} object.
+ *
+ * @param stackMap
+ * the start offset of a stack map frame in the class file.
+ * @param zip
+ * if the stack map frame at stackMap is compressed or not.
+ * @param unzip
+ * if the stack map frame must be uncompressed.
+ * @param frame
+ * where the parsed stack map frame must be stored.
+ * @return the offset of the first byte following the parsed frame.
+ */
+ private int readFrame(int stackMap, boolean zip, boolean unzip,
+ Context frame) {
+ char[] c = frame.buffer;
+ Label[] labels = frame.labels;
+ int tag;
+ int delta;
+ if (zip) {
+ tag = b[stackMap++] & 0xFF;
+ } else {
+ tag = MethodWriter.FULL_FRAME;
+ frame.offset = -1;
+ }
+ frame.localDiff = 0;
+ if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
+ delta = tag;
+ frame.mode = Opcodes.F_SAME;
+ frame.stackCount = 0;
+ } else if (tag < MethodWriter.RESERVED) {
+ delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
+ stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
+ frame.mode = Opcodes.F_SAME1;
+ frame.stackCount = 1;
+ } else {
+ delta = readUnsignedShort(stackMap);
+ stackMap += 2;
+ if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
+ frame.mode = Opcodes.F_SAME1;
+ frame.stackCount = 1;
+ } else if (tag >= MethodWriter.CHOP_FRAME
+ && tag < MethodWriter.SAME_FRAME_EXTENDED) {
+ frame.mode = Opcodes.F_CHOP;
+ frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
+ frame.localCount -= frame.localDiff;
+ frame.stackCount = 0;
+ } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
+ frame.mode = Opcodes.F_SAME;
+ frame.stackCount = 0;
+ } else if (tag < MethodWriter.FULL_FRAME) {
+ int local = unzip ? frame.localCount : 0;
+ for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
+ stackMap = readFrameType(frame.local, local++, stackMap, c,
+ labels);
+ }
+ frame.mode = Opcodes.F_APPEND;
+ frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
+ frame.localCount += frame.localDiff;
+ frame.stackCount = 0;
+ } else { // if (tag == FULL_FRAME) {
+ frame.mode = Opcodes.F_FULL;
+ int n = readUnsignedShort(stackMap);
+ stackMap += 2;
+ frame.localDiff = n;
+ frame.localCount = n;
+ for (int local = 0; n > 0; n--) {
+ stackMap = readFrameType(frame.local, local++, stackMap, c,
+ labels);
+ }
+ n = readUnsignedShort(stackMap);
+ stackMap += 2;
+ frame.stackCount = n;
+ for (int stack = 0; n > 0; n--) {
+ stackMap = readFrameType(frame.stack, stack++, stackMap, c,
+ labels);
+ }
+ }
+ }
+ frame.offset += delta + 1;
+ readLabel(frame.offset, labels);
+ return stackMap;
+ }
+
+ /**
+ * Reads a stack map frame type and stores it at the given index in the
+ * given array.
+ *
+ * @param frame
+ * the array where the parsed type must be stored.
+ * @param index
+ * the index in 'frame' where the parsed type must be stored.
+ * @param v
+ * the start offset of the stack map frame type to read.
+ * @param buf
+ * a buffer to read strings.
+ * @param labels
+ * the labels of the method currently being parsed, indexed by
+ * their offset. If the parsed type is an Uninitialized type, a
+ * new label for the corresponding NEW instruction is stored in
+ * this array if it does not already exist.
+ * @return the offset of the first byte after the parsed type.
+ */
+ private int readFrameType(final Object[] frame, final int index, int v,
+ final char[] buf, final Label[] labels) {
+ int type = b[v++] & 0xFF;
+ switch (type) {
+ case 0:
+ frame[index] = Opcodes.TOP;
+ break;
+ case 1:
+ frame[index] = Opcodes.INTEGER;
+ break;
+ case 2:
+ frame[index] = Opcodes.FLOAT;
+ break;
+ case 3:
+ frame[index] = Opcodes.DOUBLE;
+ break;
+ case 4:
+ frame[index] = Opcodes.LONG;
+ break;
+ case 5:
+ frame[index] = Opcodes.NULL;
+ break;
+ case 6:
+ frame[index] = Opcodes.UNINITIALIZED_THIS;
+ break;
+ case 7: // Object
+ frame[index] = readClass(v, buf);
+ v += 2;
+ break;
+ default: // Uninitialized
+ frame[index] = readLabel(readUnsignedShort(v), labels);
+ v += 2;
+ }
+ return v;
+ }
+
+ /**
+ * Returns the label corresponding to the given offset. The default
+ * implementation of this method creates a label for the given offset if it
+ * has not been already created.
+ *
+ * @param offset
+ * a bytecode offset in a method.
+ * @param labels
+ * the already created labels, indexed by their offset. If a
+ * label already exists for offset this method must not create a
+ * new one. Otherwise it must store the new label in this array.
+ * @return a non null Label, which must be equal to labels[offset].
+ */
+ protected Label readLabel(int offset, Label[] labels) {
+ if (labels[offset] == null) {
+ labels[offset] = new Label();
+ }
+ return labels[offset];
+ }
+
+ /**
+ * Returns the start index of the attribute_info structure of this class.
+ *
+ * @return the start index of the attribute_info structure of this class.
+ */
+ private int getAttributes() {
+ // skips the header
+ int u = header + 8 + readUnsignedShort(header + 6) * 2;
+ // skips fields and methods
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ for (int j = readUnsignedShort(u + 8); j > 0; --j) {
+ u += 6 + readInt(u + 12);
+ }
+ u += 8;
+ }
+ u += 2;
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ for (int j = readUnsignedShort(u + 8); j > 0; --j) {
+ u += 6 + readInt(u + 12);
+ }
+ u += 8;
+ }
+ // the attribute_info structure starts just after the methods
+ return u + 2;
+ }
+
+ /**
+ * Reads an attribute in {@link #b b}.
+ *
+ * @param attrs
+ * prototypes of the attributes that must be parsed during the
+ * visit of the class. Any attribute whose type is not equal to
+ * the type of one the prototypes is ignored (i.e. an empty
+ * {@link Attribute} instance is returned).
+ * @param type
+ * the type of the attribute.
+ * @param off
+ * index of the first byte of the attribute's content in
+ * {@link #b b}. The 6 attribute header bytes, containing the
+ * type and the length of the attribute, are not taken into
+ * account here (they have already been read).
+ * @param len
+ * the length of the attribute's content.
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param codeOff
+ * index of the first byte of code's attribute content in
+ * {@link #b b}, or -1 if the attribute to be read is not a code
+ * attribute. The 6 attribute header bytes, containing the type
+ * and the length of the attribute, are not taken into account
+ * here.
+ * @param labels
+ * the labels of the method's code, or null if the
+ * attribute to be read is not a code attribute.
+ * @return the attribute that has been read, or null to skip this
+ * attribute.
+ */
+ private Attribute readAttribute(final Attribute[] attrs, final String type,
+ final int off, final int len, final char[] buf, final int codeOff,
+ final Label[] labels) {
+ for (int i = 0; i < attrs.length; ++i) {
+ if (attrs[i].type.equals(type)) {
+ return attrs[i].read(this, off, len, buf, codeOff, labels);
+ }
+ }
+ return new Attribute(type).read(this, off, len, null, -1, null);
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: low level parsing
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the number of constant pool items in {@link #b b}.
+ *
+ * @return the number of constant pool items in {@link #b b}.
+ */
+ public int getItemCount() {
+ return items.length;
+ }
+
+ /**
+ * Returns the start index of the constant pool item in {@link #b b}, plus
+ * one. This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param item
+ * the index a constant pool item.
+ * @return the start index of the constant pool item in {@link #b b}, plus
+ * one.
+ */
+ public int getItem(final int item) {
+ return items[item];
+ }
+
+ /**
+ * Returns the maximum length of the strings contained in the constant pool
+ * of the class.
+ *
+ * @return the maximum length of the strings contained in the constant pool
+ * of the class.
+ */
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ /**
+ * Reads a byte value in {@link #b b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readByte(final int index) {
+ return b[index] & 0xFF;
+ }
+
+ /**
+ * Reads an unsigned short value in {@link #b b}. This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readUnsignedShort(final int index) {
+ byte[] b = this.b;
+ return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in {@link #b b}. This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public short readShort(final int index) {
+ byte[] b = this.b;
+ return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in {@link #b b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readInt(final int index) {
+ byte[] b = this.b;
+ return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
+ | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
+ }
+
+ /**
+ * Reads a signed long value in {@link #b b}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public long readLong(final int index) {
+ long l1 = readInt(index);
+ long l0 = readInt(index + 4) & 0xFFFFFFFFL;
+ return (l1 << 32) | l0;
+ }
+
+ /**
+ * Reads an UTF8 string constant pool item in {@link #b b}. This method
+ * is intended for {@link Attribute} sub classes, and is normally not needed
+ * by class generators or adapters.
+ *
+ * @param index
+ * the start index of an unsigned short value in {@link #b b},
+ * whose value is the index of an UTF8 constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 item.
+ */
+ public String readUTF8(int index, final char[] buf) {
+ int item = readUnsignedShort(index);
+ if (index == 0 || item == 0) {
+ return null;
+ }
+ String s = strings[item];
+ if (s != null) {
+ return s;
+ }
+ index = items[item];
+ return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
+ }
+
+ /**
+ * Reads UTF8 string in {@link #b b}.
+ *
+ * @param index
+ * start offset of the UTF8 string to be read.
+ * @param utfLen
+ * length of the UTF8 string to be read.
+ * @param buf
+ * buffer to be used to read the string. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 string.
+ */
+ private String readUTF(int index, final int utfLen, final char[] buf) {
+ int endIndex = index + utfLen;
+ byte[] b = this.b;
+ int strLen = 0;
+ int c;
+ int st = 0;
+ char cc = 0;
+ while (index < endIndex) {
+ c = b[index++];
+ switch (st) {
+ case 0:
+ c = c & 0xFF;
+ if (c < 0x80) { // 0xxxxxxx
+ buf[strLen++] = (char) c;
+ } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
+ cc = (char) (c & 0x1F);
+ st = 1;
+ } else { // 1110 xxxx 10xx xxxx 10xx xxxx
+ cc = (char) (c & 0x0F);
+ st = 2;
+ }
+ break;
+
+ case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
+ buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
+ st = 0;
+ break;
+
+ case 2: // byte 2 of 3-byte char
+ cc = (char) ((cc << 6) | (c & 0x3F));
+ st = 1;
+ break;
+ }
+ }
+ return new String(buf, 0, strLen);
+ }
+
+ /**
+ * Reads a class constant pool item in {@link #b b}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.
+ *
+ * @param index
+ * the start index of an unsigned short value in {@link #b b},
+ * whose value is the index of a class constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified class item.
+ */
+ public String readClass(final int index, final char[] buf) {
+ // computes the start index of the CONSTANT_Class item in b
+ // and reads the CONSTANT_Utf8 item designated by
+ // the first two bytes of this CONSTANT_Class item
+ return readUTF8(items[readUnsignedShort(index)], buf);
+ }
+
+ /**
+ * Reads a numeric or string constant pool item in {@link #b b}. This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.
+ *
+ * @param item
+ * the index of a constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
+ * {@link String}, {@link Type} or {@link Handle} corresponding to
+ * the given constant pool item.
+ */
+ public Object readConst(final int item, final char[] buf) {
+ int index = items[item];
+ switch (b[index - 1]) {
+ case ClassWriter.INT:
+ return readInt(index);
+ case ClassWriter.FLOAT:
+ return Float.intBitsToFloat(readInt(index));
+ case ClassWriter.LONG:
+ return readLong(index);
+ case ClassWriter.DOUBLE:
+ return Double.longBitsToDouble(readLong(index));
+ case ClassWriter.CLASS:
+ return Type.getObjectType(readUTF8(index, buf));
+ case ClassWriter.STR:
+ return readUTF8(index, buf);
+ case ClassWriter.MTYPE:
+ return Type.getMethodType(readUTF8(index, buf));
+ default: // case ClassWriter.HANDLE_BASE + [1..9]:
+ int tag = readByte(index);
+ int[] items = this.items;
+ int cpIndex = items[readUnsignedShort(index + 1)];
+ String owner = readClass(cpIndex, buf);
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String name = readUTF8(cpIndex, buf);
+ String desc = readUTF8(cpIndex + 2, buf);
+ return new Handle(tag, owner, name, desc);
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/ClassVisitor.java b/src/main/java/org/objectweb/asm/ClassVisitor.java
new file mode 100644
index 00000000..107ada00
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/ClassVisitor.java
@@ -0,0 +1,320 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A visitor to visit a Java class. The methods of this class must be called in
+ * the following order: visit [ visitSource ] [
+ * visitOuterClass ] ( visitAnnotation |
+ * visitTypeAnnotation | visitAttribute )* (
+ * visitInnerClass | visitField | visitMethod )*
+ * visitEnd .
+ *
+ * @author Eric Bruneton
+ */
+public abstract class ClassVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The class visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected ClassVisitor cv;
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public ClassVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param cv
+ * the class visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public ClassVisitor(final int api, final ClassVisitor cv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.cv = cv;
+ }
+
+ /**
+ * Visits the header of the class.
+ *
+ * @param version
+ * the class version.
+ * @param access
+ * the class's access flags (see {@link Opcodes}). This parameter
+ * also indicates if the class is deprecated.
+ * @param name
+ * the internal name of the class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param signature
+ * the signature of this class. May be null if the class
+ * is not a generic one, and does not extend or implement generic
+ * classes or interfaces.
+ * @param superName
+ * the internal of name of the super class (see
+ * {@link Type#getInternalName() getInternalName}). For
+ * interfaces, the super class is {@link Object}. May be
+ * null , but only for the {@link Object} class.
+ * @param interfaces
+ * the internal names of the class's interfaces (see
+ * {@link Type#getInternalName() getInternalName}). May be
+ * null .
+ */
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (cv != null) {
+ cv.visit(version, access, name, signature, superName, interfaces);
+ }
+ }
+
+ /**
+ * Visits the source of the class.
+ *
+ * @param source
+ * the name of the source file from which the class was compiled.
+ * May be null .
+ * @param debug
+ * additional debug information to compute the correspondance
+ * between source and compiled elements of the class. May be
+ * null .
+ */
+ public void visitSource(String source, String debug) {
+ if (cv != null) {
+ cv.visitSource(source, debug);
+ }
+ }
+
+ /**
+ * Visits the enclosing class of the class. This method must be called only
+ * if the class has an enclosing class.
+ *
+ * @param owner
+ * internal name of the enclosing class of the class.
+ * @param name
+ * the name of the method that contains the class, or
+ * null if the class is not enclosed in a method of its
+ * enclosing class.
+ * @param desc
+ * the descriptor of the method that contains the class, or
+ * null if the class is not enclosed in a method of its
+ * enclosing class.
+ */
+ public void visitOuterClass(String owner, String name, String desc) {
+ if (cv != null) {
+ cv.visitOuterClass(owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits an annotation of the class.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (cv != null) {
+ return cv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the class signature.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#CLASS_TYPE_PARAMETER
+ * CLASS_TYPE_PARAMETER},
+ * {@link TypeReference#CLASS_TYPE_PARAMETER_BOUND
+ * CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link TypeReference#CLASS_EXTENDS CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (cv != null) {
+ return cv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the class.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (cv != null) {
+ cv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Visits information about an inner class. This inner class is not
+ * necessarily a member of the class being visited.
+ *
+ * @param name
+ * the internal name of an inner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param outerName
+ * the internal name of the class to which the inner class
+ * belongs (see {@link Type#getInternalName() getInternalName}).
+ * May be null for not member classes.
+ * @param innerName
+ * the (simple) name of the inner class inside its enclosing
+ * class. May be null for anonymous inner classes.
+ * @param access
+ * the access flags of the inner class as originally declared in
+ * the enclosing class.
+ */
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ if (cv != null) {
+ cv.visitInnerClass(name, outerName, innerName, access);
+ }
+ }
+
+ /**
+ * Visits a field of the class.
+ *
+ * @param access
+ * the field's access flags (see {@link Opcodes}). This parameter
+ * also indicates if the field is synthetic and/or deprecated.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type Type}).
+ * @param signature
+ * the field's signature. May be null if the field's
+ * type does not use generic types.
+ * @param value
+ * the field's initial value. This parameter, which may be
+ * null if the field does not have an initial value,
+ * must be an {@link Integer}, a {@link Float}, a {@link Long}, a
+ * {@link Double} or a {@link String} (for int ,
+ * float , long or String fields
+ * respectively). This parameter is only used for static
+ * fields . Its value is ignored for non static fields, which
+ * must be initialized through bytecode instructions in
+ * constructors or methods.
+ * @return a visitor to visit field annotations and attributes, or
+ * null if this class visitor is not interested in visiting
+ * these annotations and attributes.
+ */
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ if (cv != null) {
+ return cv.visitField(access, name, desc, signature, value);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a method of the class. This method must return a new
+ * {@link MethodVisitor} instance (or null ) each time it is called,
+ * i.e., it should not return a previously returned visitor.
+ *
+ * @param access
+ * the method's access flags (see {@link Opcodes}). This
+ * parameter also indicates if the method is synthetic and/or
+ * deprecated.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param signature
+ * the method's signature. May be null if the method
+ * parameters, return type and exceptions do not use generic
+ * types.
+ * @param exceptions
+ * the internal names of the method's exception classes (see
+ * {@link Type#getInternalName() getInternalName}). May be
+ * null .
+ * @return an object to visit the byte code of the method, or null
+ * if this class visitor is not interested in visiting the code of
+ * this method.
+ */
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if (cv != null) {
+ return cv.visitMethod(access, name, desc, signature, exceptions);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the end of the class. This method, which is the last one to be
+ * called, is used to inform the visitor that all the fields and methods of
+ * the class have been visited.
+ */
+ public void visitEnd() {
+ if (cv != null) {
+ cv.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/ClassWriter.java b/src/main/java/org/objectweb/asm/ClassWriter.java
new file mode 100644
index 00000000..63e1d7e1
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/ClassWriter.java
@@ -0,0 +1,1776 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A {@link ClassVisitor} that generates classes in bytecode form. More
+ * precisely this visitor generates a byte array conforming to the Java class
+ * file format. It can be used alone, to generate a Java class "from scratch",
+ * or with one or more {@link ClassReader ClassReader} and adapter class visitor
+ * to generate a modified class from one or more existing Java classes.
+ *
+ * @author Eric Bruneton
+ */
+public class ClassWriter extends ClassVisitor {
+
+ /**
+ * Flag to automatically compute the maximum stack size and the maximum
+ * number of local variables of methods. If this flag is set, then the
+ * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the
+ * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod}
+ * method will be ignored, and computed automatically from the signature and
+ * the bytecode of each method.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_MAXS = 1;
+
+ /**
+ * Flag to automatically compute the stack map frames of methods from
+ * scratch. If this flag is set, then the calls to the
+ * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
+ * frames are recomputed from the methods bytecode. The arguments of the
+ * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
+ * recomputed from the bytecode. In other words, computeFrames implies
+ * computeMaxs.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_FRAMES = 2;
+
+ /**
+ * Pseudo access flag to distinguish between the synthetic attribute and the
+ * synthetic access flag.
+ */
+ static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
+
+ /**
+ * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC.
+ */
+ static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE
+ / Opcodes.ACC_SYNTHETIC;
+
+ /**
+ * The type of instructions without any argument.
+ */
+ static final int NOARG_INSN = 0;
+
+ /**
+ * The type of instructions with an signed byte argument.
+ */
+ static final int SBYTE_INSN = 1;
+
+ /**
+ * The type of instructions with an signed short argument.
+ */
+ static final int SHORT_INSN = 2;
+
+ /**
+ * The type of instructions with a local variable index argument.
+ */
+ static final int VAR_INSN = 3;
+
+ /**
+ * The type of instructions with an implicit local variable index argument.
+ */
+ static final int IMPLVAR_INSN = 4;
+
+ /**
+ * The type of instructions with a type descriptor argument.
+ */
+ static final int TYPE_INSN = 5;
+
+ /**
+ * The type of field and method invocations instructions.
+ */
+ static final int FIELDORMETH_INSN = 6;
+
+ /**
+ * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction.
+ */
+ static final int ITFMETH_INSN = 7;
+
+ /**
+ * The type of the INVOKEDYNAMIC instruction.
+ */
+ static final int INDYMETH_INSN = 8;
+
+ /**
+ * The type of instructions with a 2 bytes bytecode offset label.
+ */
+ static final int LABEL_INSN = 9;
+
+ /**
+ * The type of instructions with a 4 bytes bytecode offset label.
+ */
+ static final int LABELW_INSN = 10;
+
+ /**
+ * The type of the LDC instruction.
+ */
+ static final int LDC_INSN = 11;
+
+ /**
+ * The type of the LDC_W and LDC2_W instructions.
+ */
+ static final int LDCW_INSN = 12;
+
+ /**
+ * The type of the IINC instruction.
+ */
+ static final int IINC_INSN = 13;
+
+ /**
+ * The type of the TABLESWITCH instruction.
+ */
+ static final int TABL_INSN = 14;
+
+ /**
+ * The type of the LOOKUPSWITCH instruction.
+ */
+ static final int LOOK_INSN = 15;
+
+ /**
+ * The type of the MULTIANEWARRAY instruction.
+ */
+ static final int MANA_INSN = 16;
+
+ /**
+ * The type of the WIDE instruction.
+ */
+ static final int WIDE_INSN = 17;
+
+ /**
+ * The instruction types of all JVM opcodes.
+ */
+ static final byte[] TYPE;
+
+ /**
+ * The type of CONSTANT_Class constant pool items.
+ */
+ static final int CLASS = 7;
+
+ /**
+ * The type of CONSTANT_Fieldref constant pool items.
+ */
+ static final int FIELD = 9;
+
+ /**
+ * The type of CONSTANT_Methodref constant pool items.
+ */
+ static final int METH = 10;
+
+ /**
+ * The type of CONSTANT_InterfaceMethodref constant pool items.
+ */
+ static final int IMETH = 11;
+
+ /**
+ * The type of CONSTANT_String constant pool items.
+ */
+ static final int STR = 8;
+
+ /**
+ * The type of CONSTANT_Integer constant pool items.
+ */
+ static final int INT = 3;
+
+ /**
+ * The type of CONSTANT_Float constant pool items.
+ */
+ static final int FLOAT = 4;
+
+ /**
+ * The type of CONSTANT_Long constant pool items.
+ */
+ static final int LONG = 5;
+
+ /**
+ * The type of CONSTANT_Double constant pool items.
+ */
+ static final int DOUBLE = 6;
+
+ /**
+ * The type of CONSTANT_NameAndType constant pool items.
+ */
+ static final int NAME_TYPE = 12;
+
+ /**
+ * The type of CONSTANT_Utf8 constant pool items.
+ */
+ static final int UTF8 = 1;
+
+ /**
+ * The type of CONSTANT_MethodType constant pool items.
+ */
+ static final int MTYPE = 16;
+
+ /**
+ * The type of CONSTANT_MethodHandle constant pool items.
+ */
+ static final int HANDLE = 15;
+
+ /**
+ * The type of CONSTANT_InvokeDynamic constant pool items.
+ */
+ static final int INDY = 18;
+
+ /**
+ * The base value for all CONSTANT_MethodHandle constant pool items.
+ * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9
+ * different items.
+ */
+ static final int HANDLE_BASE = 20;
+
+ /**
+ * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable},
+ * instead of the constant pool, in order to avoid clashes with normal
+ * constant pool items in the ClassWriter constant pool's hash table.
+ */
+ static final int TYPE_NORMAL = 30;
+
+ /**
+ * Uninitialized type Item stored in the ClassWriter
+ * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
+ * avoid clashes with normal constant pool items in the ClassWriter constant
+ * pool's hash table.
+ */
+ static final int TYPE_UNINIT = 31;
+
+ /**
+ * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable},
+ * instead of the constant pool, in order to avoid clashes with normal
+ * constant pool items in the ClassWriter constant pool's hash table.
+ */
+ static final int TYPE_MERGED = 32;
+
+ /**
+ * The type of BootstrapMethods items. These items are stored in a special
+ * class attribute named BootstrapMethods and not in the constant pool.
+ */
+ static final int BSM = 33;
+
+ /**
+ * The class reader from which this class writer was constructed, if any.
+ */
+ ClassReader cr;
+
+ /**
+ * Minor and major version numbers of the class to be generated.
+ */
+ int version;
+
+ /**
+ * Index of the next item to be added in the constant pool.
+ */
+ int index;
+
+ /**
+ * The constant pool of this class.
+ */
+ final ByteVector pool;
+
+ /**
+ * The constant pool's hash table data.
+ */
+ Item[] items;
+
+ /**
+ * The threshold of the constant pool's hash table.
+ */
+ int threshold;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key2;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key3;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key4;
+
+ /**
+ * A type table used to temporarily store internal names that will not
+ * necessarily be stored in the constant pool. This type table is used by
+ * the control flow and data flow analysis algorithm used to compute stack
+ * map frames from scratch. This array associates to each index i
+ * the Item whose index is i . All Item objects stored in this array
+ * are also stored in the {@link #items} hash table. These two arrays allow
+ * to retrieve an Item from its index or, conversely, to get the index of an
+ * Item from its value. Each Item stores an internal name in its
+ * {@link Item#strVal1} field.
+ */
+ Item[] typeTable;
+
+ /**
+ * Number of elements in the {@link #typeTable} array.
+ */
+ private short typeCount;
+
+ /**
+ * The access flags of this class.
+ */
+ private int access;
+
+ /**
+ * The constant pool item that contains the internal name of this class.
+ */
+ private int name;
+
+ /**
+ * The internal name of this class.
+ */
+ String thisName;
+
+ /**
+ * The constant pool item that contains the signature of this class.
+ */
+ private int signature;
+
+ /**
+ * The constant pool item that contains the internal name of the super class
+ * of this class.
+ */
+ private int superName;
+
+ /**
+ * Number of interfaces implemented or extended by this class or interface.
+ */
+ private int interfaceCount;
+
+ /**
+ * The interfaces implemented or extended by this class or interface. More
+ * precisely, this array contains the indexes of the constant pool items
+ * that contain the internal names of these interfaces.
+ */
+ private int[] interfaces;
+
+ /**
+ * The index of the constant pool item that contains the name of the source
+ * file from which this class was compiled.
+ */
+ private int sourceFile;
+
+ /**
+ * The SourceDebug attribute of this class.
+ */
+ private ByteVector sourceDebug;
+
+ /**
+ * The constant pool item that contains the name of the enclosing class of
+ * this class.
+ */
+ private int enclosingMethodOwner;
+
+ /**
+ * The constant pool item that contains the name and descriptor of the
+ * enclosing method of this class.
+ */
+ private int enclosingMethod;
+
+ /**
+ * The runtime visible annotations of this class.
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this class.
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this class.
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this class.
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The non standard attributes of this class.
+ */
+ private Attribute attrs;
+
+ /**
+ * The number of entries in the InnerClasses attribute.
+ */
+ private int innerClassesCount;
+
+ /**
+ * The InnerClasses attribute.
+ */
+ private ByteVector innerClasses;
+
+ /**
+ * The number of entries in the BootstrapMethods attribute.
+ */
+ int bootstrapMethodsCount;
+
+ /**
+ * The BootstrapMethods attribute.
+ */
+ ByteVector bootstrapMethods;
+
+ /**
+ * The fields of this class. These fields are stored in a linked list of
+ * {@link FieldWriter} objects, linked to each other by their
+ * {@link FieldWriter#fv} field. This field stores the first element of this
+ * list.
+ */
+ FieldWriter firstField;
+
+ /**
+ * The fields of this class. These fields are stored in a linked list of
+ * {@link FieldWriter} objects, linked to each other by their
+ * {@link FieldWriter#fv} field. This field stores the last element of this
+ * list.
+ */
+ FieldWriter lastField;
+
+ /**
+ * The methods of this class. These methods are stored in a linked list of
+ * {@link MethodWriter} objects, linked to each other by their
+ * {@link MethodWriter#mv} field. This field stores the first element of
+ * this list.
+ */
+ MethodWriter firstMethod;
+
+ /**
+ * The methods of this class. These methods are stored in a linked list of
+ * {@link MethodWriter} objects, linked to each other by their
+ * {@link MethodWriter#mv} field. This field stores the last element of this
+ * list.
+ */
+ MethodWriter lastMethod;
+
+ /**
+ * true if the maximum stack size and number of local variables
+ * must be automatically computed.
+ */
+ private boolean computeMaxs;
+
+ /**
+ * true if the stack map frames must be recomputed from scratch.
+ */
+ private boolean computeFrames;
+
+ /**
+ * true if the stack map tables of this class are invalid. The
+ * {@link MethodWriter#resizeInstructions} method cannot transform existing
+ * stack map tables, and so produces potentially invalid classes when it is
+ * executed. In this case the class is reread and rewritten with the
+ * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize
+ * stack map tables when this option is used).
+ */
+ boolean invalidFrames;
+
+ // ------------------------------------------------------------------------
+ // Static initializer
+ // ------------------------------------------------------------------------
+
+ /**
+ * Computes the instruction types of JVM opcodes.
+ */
+ static {
+ int i;
+ byte[] b = new byte[220];
+ String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
+ + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA"
+ + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ";
+ for (i = 0; i < b.length; ++i) {
+ b[i] = (byte) (s.charAt(i) - 'A');
+ }
+ TYPE = b;
+
+ // code to generate the above string
+ //
+ // // SBYTE_INSN instructions
+ // b[Constants.NEWARRAY] = SBYTE_INSN;
+ // b[Constants.BIPUSH] = SBYTE_INSN;
+ //
+ // // SHORT_INSN instructions
+ // b[Constants.SIPUSH] = SHORT_INSN;
+ //
+ // // (IMPL)VAR_INSN instructions
+ // b[Constants.RET] = VAR_INSN;
+ // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) {
+ // b[i] = VAR_INSN;
+ // }
+ // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) {
+ // b[i] = VAR_INSN;
+ // }
+ // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3
+ // b[i] = IMPLVAR_INSN;
+ // }
+ // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3
+ // b[i] = IMPLVAR_INSN;
+ // }
+ //
+ // // TYPE_INSN instructions
+ // b[Constants.NEW] = TYPE_INSN;
+ // b[Constants.ANEWARRAY] = TYPE_INSN;
+ // b[Constants.CHECKCAST] = TYPE_INSN;
+ // b[Constants.INSTANCEOF] = TYPE_INSN;
+ //
+ // // (Set)FIELDORMETH_INSN instructions
+ // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) {
+ // b[i] = FIELDORMETH_INSN;
+ // }
+ // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN;
+ // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN;
+ //
+ // // LABEL(W)_INSN instructions
+ // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) {
+ // b[i] = LABEL_INSN;
+ // }
+ // b[Constants.IFNULL] = LABEL_INSN;
+ // b[Constants.IFNONNULL] = LABEL_INSN;
+ // b[200] = LABELW_INSN; // GOTO_W
+ // b[201] = LABELW_INSN; // JSR_W
+ // // temporary opcodes used internally by ASM - see Label and
+ // MethodWriter
+ // for (i = 202; i < 220; ++i) {
+ // b[i] = LABEL_INSN;
+ // }
+ //
+ // // LDC(_W) instructions
+ // b[Constants.LDC] = LDC_INSN;
+ // b[19] = LDCW_INSN; // LDC_W
+ // b[20] = LDCW_INSN; // LDC2_W
+ //
+ // // special instructions
+ // b[Constants.IINC] = IINC_INSN;
+ // b[Constants.TABLESWITCH] = TABL_INSN;
+ // b[Constants.LOOKUPSWITCH] = LOOK_INSN;
+ // b[Constants.MULTIANEWARRAY] = MANA_INSN;
+ // b[196] = WIDE_INSN; // WIDE
+ //
+ // for (i = 0; i < b.length; ++i) {
+ // System.err.print((char)('A' + b[i]));
+ // }
+ // System.err.println();
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassWriter} object.
+ *
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #COMPUTE_MAXS},
+ * {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final int flags) {
+ super(Opcodes.ASM5);
+ index = 1;
+ pool = new ByteVector();
+ items = new Item[256];
+ threshold = (int) (0.75d * items.length);
+ key = new Item();
+ key2 = new Item();
+ key3 = new Item();
+ key4 = new Item();
+ this.computeMaxs = (flags & COMPUTE_MAXS) != 0;
+ this.computeFrames = (flags & COMPUTE_FRAMES) != 0;
+ }
+
+ /**
+ * Constructs a new {@link ClassWriter} object and enables optimizations for
+ * "mostly add" bytecode transformations. These optimizations are the
+ * following:
+ *
+ *
+ * The constant pool from the original class is copied as is in the new
+ * class, which saves time. New constant pool entries will be added at the
+ * end if necessary, but unused constant pool entries won't be
+ * removed .
+ * Methods that are not transformed are copied as is in the new class,
+ * directly from the original class bytecode (i.e. without emitting visit
+ * events for all the method instructions), which saves a lot of
+ * time. Untransformed methods are detected by the fact that the
+ * {@link ClassReader} receives {@link MethodVisitor} objects that come from
+ * a {@link ClassWriter} (and not from any other {@link ClassVisitor}
+ * instance).
+ *
+ *
+ * @param classReader
+ * the {@link ClassReader} used to read the original class. It
+ * will be used to copy the entire constant pool from the
+ * original class and also to copy other fragments of original
+ * bytecode where applicable.
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. These option flags do not affect methods
+ * that are copied as is in the new class. This means that the
+ * maximum stack size nor the stack frames will be computed for
+ * these methods . See {@link #COMPUTE_MAXS},
+ * {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final ClassReader classReader, final int flags) {
+ this(flags);
+ classReader.copyPool(this);
+ this.cr = classReader;
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the ClassVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public final void visit(final int version, final int access,
+ final String name, final String signature, final String superName,
+ final String[] interfaces) {
+ this.version = version;
+ this.access = access;
+ this.name = newClass(name);
+ thisName = name;
+ if (ClassReader.SIGNATURES && signature != null) {
+ this.signature = newUTF8(signature);
+ }
+ this.superName = superName == null ? 0 : newClass(superName);
+ if (interfaces != null && interfaces.length > 0) {
+ interfaceCount = interfaces.length;
+ this.interfaces = new int[interfaceCount];
+ for (int i = 0; i < interfaceCount; ++i) {
+ this.interfaces[i] = newClass(interfaces[i]);
+ }
+ }
+ }
+
+ @Override
+ public final void visitSource(final String file, final String debug) {
+ if (file != null) {
+ sourceFile = newUTF8(file);
+ }
+ if (debug != null) {
+ sourceDebug = new ByteVector().encodeUTF8(debug, 0,
+ Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public final void visitOuterClass(final String owner, final String name,
+ final String desc) {
+ enclosingMethodOwner = newClass(owner);
+ if (name != null && desc != null) {
+ enclosingMethod = newNameType(name, desc);
+ }
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public final AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public final void visitAttribute(final Attribute attr) {
+ attr.next = attrs;
+ attrs = attr;
+ }
+
+ @Override
+ public final void visitInnerClass(final String name,
+ final String outerName, final String innerName, final int access) {
+ if (innerClasses == null) {
+ innerClasses = new ByteVector();
+ }
+ // Sec. 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the
+ // constant_pool table which represents a class or interface C that is
+ // not a package member must have exactly one corresponding entry in the
+ // classes array". To avoid duplicates we keep track in the intVal field
+ // of the Item of each CONSTANT_Class_info entry C whether an inner
+ // class entry has already been added for C (this field is unused for
+ // class entries, and changing its value does not change the hashcode
+ // and equality tests). If so we store the index of this inner class
+ // entry (plus one) in intVal. This hack allows duplicate detection in
+ // O(1) time.
+ Item nameItem = newClassItem(name);
+ if (nameItem.intVal == 0) {
+ ++innerClassesCount;
+ innerClasses.putShort(nameItem.index);
+ innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
+ innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
+ innerClasses.putShort(access);
+ nameItem.intVal = innerClassesCount;
+ } else {
+ // Compare the inner classes entry nameItem.intVal - 1 with the
+ // arguments of this method and throw an exception if there is a
+ // difference?
+ }
+ }
+
+ @Override
+ public final FieldVisitor visitField(final int access, final String name,
+ final String desc, final String signature, final Object value) {
+ return new FieldWriter(this, access, name, desc, signature, value);
+ }
+
+ @Override
+ public final MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature, final String[] exceptions) {
+ return new MethodWriter(this, access, name, desc, signature,
+ exceptions, computeMaxs, computeFrames);
+ }
+
+ @Override
+ public final void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Other public methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the bytecode of the class that was build with this class writer.
+ *
+ * @return the bytecode of the class that was build with this class writer.
+ */
+ public byte[] toByteArray() {
+ if (index > 0xFFFF) {
+ throw new RuntimeException("Class file too large!");
+ }
+ // computes the real size of the bytecode of this class
+ int size = 24 + 2 * interfaceCount;
+ int nbFields = 0;
+ FieldWriter fb = firstField;
+ while (fb != null) {
+ ++nbFields;
+ size += fb.getSize();
+ fb = (FieldWriter) fb.fv;
+ }
+ int nbMethods = 0;
+ MethodWriter mb = firstMethod;
+ while (mb != null) {
+ ++nbMethods;
+ size += mb.getSize();
+ mb = (MethodWriter) mb.mv;
+ }
+ int attributeCount = 0;
+ if (bootstrapMethods != null) {
+ // we put it as first attribute in order to improve a bit
+ // ClassReader.copyBootstrapMethods
+ ++attributeCount;
+ size += 8 + bootstrapMethods.length;
+ newUTF8("BootstrapMethods");
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ ++attributeCount;
+ size += 8;
+ newUTF8("Signature");
+ }
+ if (sourceFile != 0) {
+ ++attributeCount;
+ size += 8;
+ newUTF8("SourceFile");
+ }
+ if (sourceDebug != null) {
+ ++attributeCount;
+ size += sourceDebug.length + 6;
+ newUTF8("SourceDebugExtension");
+ }
+ if (enclosingMethodOwner != 0) {
+ ++attributeCount;
+ size += 10;
+ newUTF8("EnclosingMethod");
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ size += 6;
+ newUTF8("Deprecated");
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((version & 0xFFFF) < Opcodes.V1_5
+ || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ size += 6;
+ newUTF8("Synthetic");
+ }
+ }
+ if (innerClasses != null) {
+ ++attributeCount;
+ size += 8 + innerClasses.length;
+ newUTF8("InnerClasses");
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ size += 8 + anns.getSize();
+ newUTF8("RuntimeVisibleAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ size += 8 + ianns.getSize();
+ newUTF8("RuntimeInvisibleAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ size += 8 + tanns.getSize();
+ newUTF8("RuntimeVisibleTypeAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ size += 8 + itanns.getSize();
+ newUTF8("RuntimeInvisibleTypeAnnotations");
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ size += attrs.getSize(this, null, 0, -1, -1);
+ }
+ size += pool.length;
+ // allocates a byte vector of this size, in order to avoid unnecessary
+ // arraycopy operations in the ByteVector.enlarge() method
+ ByteVector out = new ByteVector(size);
+ out.putInt(0xCAFEBABE).putInt(version);
+ out.putShort(index).putByteArray(pool.data, 0, pool.length);
+ int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
+ out.putShort(access & ~mask).putShort(name).putShort(superName);
+ out.putShort(interfaceCount);
+ for (int i = 0; i < interfaceCount; ++i) {
+ out.putShort(interfaces[i]);
+ }
+ out.putShort(nbFields);
+ fb = firstField;
+ while (fb != null) {
+ fb.put(out);
+ fb = (FieldWriter) fb.fv;
+ }
+ out.putShort(nbMethods);
+ mb = firstMethod;
+ while (mb != null) {
+ mb.put(out);
+ mb = (MethodWriter) mb.mv;
+ }
+ out.putShort(attributeCount);
+ if (bootstrapMethods != null) {
+ out.putShort(newUTF8("BootstrapMethods"));
+ out.putInt(bootstrapMethods.length + 2).putShort(
+ bootstrapMethodsCount);
+ out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
+ }
+ if (sourceFile != 0) {
+ out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
+ }
+ if (sourceDebug != null) {
+ int len = sourceDebug.length;
+ out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
+ out.putByteArray(sourceDebug.data, 0, len);
+ }
+ if (enclosingMethodOwner != 0) {
+ out.putShort(newUTF8("EnclosingMethod")).putInt(4);
+ out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(newUTF8("Deprecated")).putInt(0);
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((version & 0xFFFF) < Opcodes.V1_5
+ || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if (innerClasses != null) {
+ out.putShort(newUTF8("InnerClasses"));
+ out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
+ out.putByteArray(innerClasses.data, 0, innerClasses.length);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (attrs != null) {
+ attrs.put(this, null, 0, -1, -1, out);
+ }
+ if (invalidFrames) {
+ anns = null;
+ ianns = null;
+ attrs = null;
+ innerClassesCount = 0;
+ innerClasses = null;
+ bootstrapMethodsCount = 0;
+ bootstrapMethods = null;
+ firstField = null;
+ lastField = null;
+ firstMethod = null;
+ lastMethod = null;
+ computeMaxs = false;
+ computeFrames = true;
+ invalidFrames = false;
+ new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);
+ return toByteArray();
+ }
+ return out.data;
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: constant pool management
+ // ------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param cst
+ * the value of the constant to be added to the constant pool.
+ * This parameter must be an {@link Integer}, a {@link Float}, a
+ * {@link Long}, a {@link Double}, a {@link String} or a
+ * {@link Type}.
+ * @return a new or already existing constant item with the given value.
+ */
+ Item newConstItem(final Object cst) {
+ if (cst instanceof Integer) {
+ int val = ((Integer) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Byte) {
+ int val = ((Byte) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Character) {
+ int val = ((Character) cst).charValue();
+ return newInteger(val);
+ } else if (cst instanceof Short) {
+ int val = ((Short) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Boolean) {
+ int val = ((Boolean) cst).booleanValue() ? 1 : 0;
+ return newInteger(val);
+ } else if (cst instanceof Float) {
+ float val = ((Float) cst).floatValue();
+ return newFloat(val);
+ } else if (cst instanceof Long) {
+ long val = ((Long) cst).longValue();
+ return newLong(val);
+ } else if (cst instanceof Double) {
+ double val = ((Double) cst).doubleValue();
+ return newDouble(val);
+ } else if (cst instanceof String) {
+ return newString((String) cst);
+ } else if (cst instanceof Type) {
+ Type t = (Type) cst;
+ int s = t.getSort();
+ if (s == Type.OBJECT) {
+ return newClassItem(t.getInternalName());
+ } else if (s == Type.METHOD) {
+ return newMethodTypeItem(t.getDescriptor());
+ } else { // s == primitive type or array
+ return newClassItem(t.getDescriptor());
+ }
+ } else if (cst instanceof Handle) {
+ Handle h = (Handle) cst;
+ return newHandleItem(h.tag, h.owner, h.name, h.desc);
+ } else {
+ throw new IllegalArgumentException("value " + cst);
+ }
+ }
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param cst
+ * the value of the constant to be added to the constant pool.
+ * This parameter must be an {@link Integer}, a {@link Float}, a
+ * {@link Long}, a {@link Double} or a {@link String}.
+ * @return the index of a new or already existing constant item with the
+ * given value.
+ */
+ public int newConst(final Object cst) {
+ return newConstItem(cst).index;
+ }
+
+ /**
+ * Adds an UTF8 string to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item. This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.
+ *
+ * @param value
+ * the String value.
+ * @return the index of a new or already existing UTF8 item.
+ */
+ public int newUTF8(final String value) {
+ key.set(UTF8, value, null, null);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(UTF8).putUTF8(value);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param value
+ * the internal name of the class.
+ * @return a new or already existing class reference item.
+ */
+ Item newClassItem(final String value) {
+ key2.set(CLASS, value, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(CLASS, newUTF8(value));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param value
+ * the internal name of the class.
+ * @return the index of a new or already existing class reference item.
+ */
+ public int newClass(final String value) {
+ return newClassItem(value).index;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param methodDesc
+ * method descriptor of the method type.
+ * @return a new or already existing method type reference item.
+ */
+ Item newMethodTypeItem(final String methodDesc) {
+ key2.set(MTYPE, methodDesc, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(MTYPE, newUTF8(methodDesc));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param methodDesc
+ * method descriptor of the method type.
+ * @return the index of a new or already existing method type reference
+ * item.
+ */
+ public int newMethodType(final String methodDesc) {
+ return newMethodTypeItem(methodDesc).index;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.
+ *
+ * @param tag
+ * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the field or method owner class.
+ * @param name
+ * the name of the field or method.
+ * @param desc
+ * the descriptor of the field or method.
+ * @return a new or an already existing method type reference item.
+ */
+ Item newHandleItem(final int tag, final String owner, final String name,
+ final String desc) {
+ key4.set(HANDLE_BASE + tag, owner, name, desc);
+ Item result = get(key4);
+ if (result == null) {
+ if (tag <= Opcodes.H_PUTSTATIC) {
+ put112(HANDLE, tag, newField(owner, name, desc));
+ } else {
+ put112(HANDLE,
+ tag,
+ newMethod(owner, name, desc,
+ tag == Opcodes.H_INVOKEINTERFACE));
+ }
+ result = new Item(index++, key4);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.
+ *
+ * @param tag
+ * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the field or method owner class.
+ * @param name
+ * the name of the field or method.
+ * @param desc
+ * the descriptor of the field or method.
+ * @return the index of a new or already existing method type reference
+ * item.
+ */
+ public int newHandle(final int tag, final String owner, final String name,
+ final String desc) {
+ return newHandleItem(tag, owner, name, desc).index;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param name
+ * name of the invoked method.
+ * @param desc
+ * descriptor of the invoke method.
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments.
+ *
+ * @return a new or an already existing invokedynamic type reference item.
+ */
+ Item newInvokeDynamicItem(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ // cache for performance
+ ByteVector bootstrapMethods = this.bootstrapMethods;
+ if (bootstrapMethods == null) {
+ bootstrapMethods = this.bootstrapMethods = new ByteVector();
+ }
+
+ int position = bootstrapMethods.length; // record current position
+
+ int hashCode = bsm.hashCode();
+ bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
+ bsm.desc));
+
+ int argsLength = bsmArgs.length;
+ bootstrapMethods.putShort(argsLength);
+
+ for (int i = 0; i < argsLength; i++) {
+ Object bsmArg = bsmArgs[i];
+ hashCode ^= bsmArg.hashCode();
+ bootstrapMethods.putShort(newConst(bsmArg));
+ }
+
+ byte[] data = bootstrapMethods.data;
+ int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments)
+ hashCode &= 0x7FFFFFFF;
+ Item result = items[hashCode % items.length];
+ loop: while (result != null) {
+ if (result.type != BSM || result.hashCode != hashCode) {
+ result = result.next;
+ continue;
+ }
+
+ // because the data encode the size of the argument
+ // we don't need to test if these size are equals
+ int resultPosition = result.intVal;
+ for (int p = 0; p < length; p++) {
+ if (data[position + p] != data[resultPosition + p]) {
+ result = result.next;
+ continue loop;
+ }
+ }
+ break;
+ }
+
+ int bootstrapMethodIndex;
+ if (result != null) {
+ bootstrapMethodIndex = result.index;
+ bootstrapMethods.length = position; // revert to old position
+ } else {
+ bootstrapMethodIndex = bootstrapMethodsCount++;
+ result = new Item(bootstrapMethodIndex);
+ result.set(position, hashCode);
+ put(result);
+ }
+
+ // now, create the InvokeDynamic constant
+ key3.set(name, desc, bootstrapMethodIndex);
+ result = get(key3);
+ if (result == null) {
+ put122(INDY, bootstrapMethodIndex, newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param name
+ * name of the invoked method.
+ * @param desc
+ * descriptor of the invoke method.
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments.
+ *
+ * @return the index of a new or already existing invokedynamic reference
+ * item.
+ */
+ public int newInvokeDynamic(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner
+ * the internal name of the field's owner class.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor.
+ * @return a new or already existing field reference item.
+ */
+ Item newFieldItem(final String owner, final String name, final String desc) {
+ key3.set(FIELD, owner, name, desc);
+ Item result = get(key3);
+ if (result == null) {
+ put122(FIELD, newClass(owner), newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param owner
+ * the internal name of the field's owner class.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor.
+ * @return the index of a new or already existing field reference item.
+ */
+ public int newField(final String owner, final String name, final String desc) {
+ return newFieldItem(owner, name, desc).index;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner
+ * the internal name of the method's owner class.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor.
+ * @param itf
+ * true if owner is an interface.
+ * @return a new or already existing method reference item.
+ */
+ Item newMethodItem(final String owner, final String name,
+ final String desc, final boolean itf) {
+ int type = itf ? IMETH : METH;
+ key3.set(type, owner, name, desc);
+ Item result = get(key3);
+ if (result == null) {
+ put122(type, newClass(owner), newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @param owner
+ * the internal name of the method's owner class.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor.
+ * @param itf
+ * true if owner is an interface.
+ * @return the index of a new or already existing method reference item.
+ */
+ public int newMethod(final String owner, final String name,
+ final String desc, final boolean itf) {
+ return newMethodItem(owner, name, desc, itf).index;
+ }
+
+ /**
+ * Adds an integer to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the int value.
+ * @return a new or already existing int item.
+ */
+ Item newInteger(final int value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(INT).putInt(value);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a float to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the float value.
+ * @return a new or already existing float item.
+ */
+ Item newFloat(final float value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(FLOAT).putInt(key.intVal);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a long to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the long value.
+ * @return a new or already existing long item.
+ */
+ Item newLong(final long value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(LONG).putLong(value);
+ result = new Item(index, key);
+ index += 2;
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a double to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the double value.
+ * @return a new or already existing double item.
+ */
+ Item newDouble(final double value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(DOUBLE).putLong(key.longVal);
+ result = new Item(index, key);
+ index += 2;
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a string to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the String value.
+ * @return a new or already existing string item.
+ */
+ private Item newString(final String value) {
+ key2.set(STR, value, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(STR, newUTF8(value));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item. This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.
+ *
+ * @param name
+ * a name.
+ * @param desc
+ * a type descriptor.
+ * @return the index of a new or already existing name and type item.
+ */
+ public int newNameType(final String name, final String desc) {
+ return newNameTypeItem(name, desc).index;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param name
+ * a name.
+ * @param desc
+ * a type descriptor.
+ * @return a new or already existing name and type item.
+ */
+ Item newNameTypeItem(final String name, final String desc) {
+ key2.set(NAME_TYPE, name, desc, null);
+ Item result = get(key2);
+ if (result == null) {
+ put122(NAME_TYPE, newUTF8(name), newUTF8(desc));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds the given internal name to {@link #typeTable} and returns its index.
+ * Does nothing if the type table already contains this internal name.
+ *
+ * @param type
+ * the internal name to be added to the type table.
+ * @return the index of this internal name in the type table.
+ */
+ int addType(final String type) {
+ key.set(TYPE_NORMAL, type, null, null);
+ Item result = get(key);
+ if (result == null) {
+ result = addType(key);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds the given "uninitialized" type to {@link #typeTable} and returns its
+ * index. This method is used for UNINITIALIZED types, made of an internal
+ * name and a bytecode offset.
+ *
+ * @param type
+ * the internal name to be added to the type table.
+ * @param offset
+ * the bytecode offset of the NEW instruction that created this
+ * UNINITIALIZED type value.
+ * @return the index of this internal name in the type table.
+ */
+ int addUninitializedType(final String type, final int offset) {
+ key.type = TYPE_UNINIT;
+ key.intVal = offset;
+ key.strVal1 = type;
+ key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset);
+ Item result = get(key);
+ if (result == null) {
+ result = addType(key);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds the given Item to {@link #typeTable}.
+ *
+ * @param item
+ * the value to be added to the type table.
+ * @return the added Item, which a new Item instance with the same value as
+ * the given Item.
+ */
+ private Item addType(final Item item) {
+ ++typeCount;
+ Item result = new Item(typeCount, key);
+ put(result);
+ if (typeTable == null) {
+ typeTable = new Item[16];
+ }
+ if (typeCount == typeTable.length) {
+ Item[] newTable = new Item[2 * typeTable.length];
+ System.arraycopy(typeTable, 0, newTable, 0, typeTable.length);
+ typeTable = newTable;
+ }
+ typeTable[typeCount] = result;
+ return result;
+ }
+
+ /**
+ * Returns the index of the common super type of the two given types. This
+ * method calls {@link #getCommonSuperClass} and caches the result in the
+ * {@link #items} hash table to speedup future calls with the same
+ * parameters.
+ *
+ * @param type1
+ * index of an internal name in {@link #typeTable}.
+ * @param type2
+ * index of an internal name in {@link #typeTable}.
+ * @return the index of the common super type of the two given types.
+ */
+ int getMergedType(final int type1, final int type2) {
+ key2.type = TYPE_MERGED;
+ key2.longVal = type1 | (((long) type2) << 32);
+ key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2);
+ Item result = get(key2);
+ if (result == null) {
+ String t = typeTable[type1].strVal1;
+ String u = typeTable[type2].strVal1;
+ key2.intVal = addType(getCommonSuperClass(t, u));
+ result = new Item((short) 0, key2);
+ put(result);
+ }
+ return result.intVal;
+ }
+
+ /**
+ * Returns the common super type of the two given types. The default
+ * implementation of this method loads the two given classes and uses
+ * the java.lang.Class methods to find the common super class. It can be
+ * overridden to compute this common super type in other ways, in particular
+ * without actually loading any class, or to take into account the class
+ * that is currently being generated by this ClassWriter, which can of
+ * course not be loaded since it is under construction.
+ *
+ * @param type1
+ * the internal name of a class.
+ * @param type2
+ * the internal name of another class.
+ * @return the internal name of the common super class of the two given
+ * classes.
+ */
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ Class> c, d;
+ ClassLoader classLoader = getClass().getClassLoader();
+ try {
+ c = Class.forName(type1.replace('/', '.'), false, classLoader);
+ d = Class.forName(type2.replace('/', '.'), false, classLoader);
+ } catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ if (c.isAssignableFrom(d)) {
+ return type1;
+ }
+ if (d.isAssignableFrom(c)) {
+ return type2;
+ }
+ if (c.isInterface() || d.isInterface()) {
+ return "java/lang/Object";
+ } else {
+ do {
+ c = c.getSuperclass();
+ } while (!c.isAssignableFrom(d));
+ return c.getName().replace('.', '/');
+ }
+ }
+
+ /**
+ * Returns the constant pool's hash table item which is equal to the given
+ * item.
+ *
+ * @param key
+ * a constant pool item.
+ * @return the constant pool's hash table item which is equal to the given
+ * item, or null if there is no such item.
+ */
+ private Item get(final Item key) {
+ Item i = items[key.hashCode % items.length];
+ while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
+ i = i.next;
+ }
+ return i;
+ }
+
+ /**
+ * Puts the given item in the constant pool's hash table. The hash table
+ * must not already contains this item.
+ *
+ * @param i
+ * the item to be added to the constant pool's hash table.
+ */
+ private void put(final Item i) {
+ if (index + typeCount > threshold) {
+ int ll = items.length;
+ int nl = ll * 2 + 1;
+ Item[] newItems = new Item[nl];
+ for (int l = ll - 1; l >= 0; --l) {
+ Item j = items[l];
+ while (j != null) {
+ int index = j.hashCode % newItems.length;
+ Item k = j.next;
+ j.next = newItems[index];
+ newItems[index] = j;
+ j = k;
+ }
+ }
+ items = newItems;
+ threshold = (int) (nl * 0.75);
+ }
+ int index = i.hashCode % items.length;
+ i.next = items[index];
+ items[index] = i;
+ }
+
+ /**
+ * Puts one byte and two shorts into the constant pool.
+ *
+ * @param b
+ * a byte.
+ * @param s1
+ * a short.
+ * @param s2
+ * another short.
+ */
+ private void put122(final int b, final int s1, final int s2) {
+ pool.put12(b, s1).putShort(s2);
+ }
+
+ /**
+ * Puts two bytes and one short into the constant pool.
+ *
+ * @param b1
+ * a byte.
+ * @param b2
+ * another byte.
+ * @param s
+ * a short.
+ */
+ private void put112(final int b1, final int b2, final int s) {
+ pool.put11(b1, b2).putShort(s);
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Context.java b/src/main/java/org/objectweb/asm/Context.java
new file mode 100644
index 00000000..2702784b
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Context.java
@@ -0,0 +1,145 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.objectweb.asm;
+
+/**
+ * Information about a class being parsed in a {@link ClassReader}.
+ *
+ * @author Eric Bruneton
+ */
+class Context {
+
+ /**
+ * Prototypes of the attributes that must be parsed for this class.
+ */
+ Attribute[] attrs;
+
+ /**
+ * The {@link ClassReader} option flags for the parsing of this class.
+ */
+ int flags;
+
+ /**
+ * The buffer used to read strings.
+ */
+ char[] buffer;
+
+ /**
+ * The start index of each bootstrap method.
+ */
+ int[] bootstrapMethods;
+
+ /**
+ * The access flags of the method currently being parsed.
+ */
+ int access;
+
+ /**
+ * The name of the method currently being parsed.
+ */
+ String name;
+
+ /**
+ * The descriptor of the method currently being parsed.
+ */
+ String desc;
+
+ /**
+ * The label objects, indexed by bytecode offset, of the method currently
+ * being parsed (only bytecode offsets for which a label is needed have a
+ * non null associated Label object).
+ */
+ Label[] labels;
+
+ /**
+ * The target of the type annotation currently being parsed.
+ */
+ int typeRef;
+
+ /**
+ * The path of the type annotation currently being parsed.
+ */
+ TypePath typePath;
+
+ /**
+ * The offset of the latest stack map frame that has been parsed.
+ */
+ int offset;
+
+ /**
+ * The labels corresponding to the start of the local variable ranges in the
+ * local variable type annotation currently being parsed.
+ */
+ Label[] start;
+
+ /**
+ * The labels corresponding to the end of the local variable ranges in the
+ * local variable type annotation currently being parsed.
+ */
+ Label[] end;
+
+ /**
+ * The local variable indices for each local variable range in the local
+ * variable type annotation currently being parsed.
+ */
+ int[] index;
+
+ /**
+ * The encoding of the latest stack map frame that has been parsed.
+ */
+ int mode;
+
+ /**
+ * The number of locals in the latest stack map frame that has been parsed.
+ */
+ int localCount;
+
+ /**
+ * The number locals in the latest stack map frame that has been parsed,
+ * minus the number of locals in the previous frame.
+ */
+ int localDiff;
+
+ /**
+ * The local values of the latest stack map frame that has been parsed.
+ */
+ Object[] local;
+
+ /**
+ * The stack size of the latest stack map frame that has been parsed.
+ */
+ int stackCount;
+
+ /**
+ * The stack values of the latest stack map frame that has been parsed.
+ */
+ Object[] stack;
+}
\ No newline at end of file
diff --git a/src/main/java/org/objectweb/asm/Edge.java b/src/main/java/org/objectweb/asm/Edge.java
new file mode 100644
index 00000000..4e87cbab
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Edge.java
@@ -0,0 +1,75 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * An edge in the control flow graph of a method body. See {@link Label Label}.
+ *
+ * @author Eric Bruneton
+ */
+class Edge {
+
+ /**
+ * Denotes a normal control flow graph edge.
+ */
+ static final int NORMAL = 0;
+
+ /**
+ * Denotes a control flow graph edge corresponding to an exception handler.
+ * More precisely any {@link Edge} whose {@link #info} is strictly positive
+ * corresponds to an exception handler. The actual value of {@link #info} is
+ * the index, in the {@link ClassWriter} type table, of the exception that
+ * is catched.
+ */
+ static final int EXCEPTION = 0x7FFFFFFF;
+
+ /**
+ * Information about this control flow graph edge. If
+ * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative)
+ * stack size in the basic block from which this edge originates. This size
+ * is equal to the stack size at the "jump" instruction to which this edge
+ * corresponds, relatively to the stack size at the beginning of the
+ * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used,
+ * this field is the kind of this control flow graph edge (i.e. NORMAL or
+ * EXCEPTION).
+ */
+ int info;
+
+ /**
+ * The successor block of the basic block from which this edge originates.
+ */
+ Label successor;
+
+ /**
+ * The next edge in the list of successors of the originating basic block.
+ * See {@link Label#successors successors}.
+ */
+ Edge next;
+}
diff --git a/src/main/java/org/objectweb/asm/FieldVisitor.java b/src/main/java/org/objectweb/asm/FieldVisitor.java
new file mode 100644
index 00000000..2372e4c7
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/FieldVisitor.java
@@ -0,0 +1,150 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A visitor to visit a Java field. The methods of this class must be called in
+ * the following order: ( visitAnnotation |
+ * visitTypeAnnotation | visitAttribute )* visitEnd .
+ *
+ * @author Eric Bruneton
+ */
+public abstract class FieldVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The field visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected FieldVisitor fv;
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public FieldVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param fv
+ * the field visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public FieldVisitor(final int api, final FieldVisitor fv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.fv = fv;
+ }
+
+ /**
+ * Visits an annotation of the field.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (fv != null) {
+ return fv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on the type of the field.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#FIELD FIELD}. See
+ * {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (fv != null) {
+ return fv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the field.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (fv != null) {
+ fv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Visits the end of the field. This method, which is the last one to be
+ * called, is used to inform the visitor that all the annotations and
+ * attributes of the field have been visited.
+ */
+ public void visitEnd() {
+ if (fv != null) {
+ fv.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/FieldWriter.java b/src/main/java/org/objectweb/asm/FieldWriter.java
new file mode 100644
index 00000000..84d92aae
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/FieldWriter.java
@@ -0,0 +1,329 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * An {@link FieldVisitor} that generates Java fields in bytecode form.
+ *
+ * @author Eric Bruneton
+ */
+final class FieldWriter extends FieldVisitor {
+
+ /**
+ * The class writer to which this field must be added.
+ */
+ private final ClassWriter cw;
+
+ /**
+ * Access flags of this field.
+ */
+ private final int access;
+
+ /**
+ * The index of the constant pool item that contains the name of this
+ * method.
+ */
+ private final int name;
+
+ /**
+ * The index of the constant pool item that contains the descriptor of this
+ * field.
+ */
+ private final int desc;
+
+ /**
+ * The index of the constant pool item that contains the signature of this
+ * field.
+ */
+ private int signature;
+
+ /**
+ * The index of the constant pool item that contains the constant value of
+ * this field.
+ */
+ private int value;
+
+ /**
+ * The runtime visible annotations of this field. May be null .
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this field. May be null .
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this field. May be null .
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this field. May be
+ * null .
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The non standard attributes of this field. May be null .
+ */
+ private Attribute attrs;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link FieldWriter}.
+ *
+ * @param cw
+ * the class writer to which this field must be added.
+ * @param access
+ * the field's access flags (see {@link Opcodes}).
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type}).
+ * @param signature
+ * the field's signature. May be null .
+ * @param value
+ * the field's constant value. May be null .
+ */
+ FieldWriter(final ClassWriter cw, final int access, final String name,
+ final String desc, final String signature, final Object value) {
+ super(Opcodes.ASM5);
+ if (cw.firstField == null) {
+ cw.firstField = this;
+ } else {
+ cw.lastField.fv = this;
+ }
+ cw.lastField = this;
+ this.cw = cw;
+ this.access = access;
+ this.name = cw.newUTF8(name);
+ this.desc = cw.newUTF8(desc);
+ if (ClassReader.SIGNATURES && signature != null) {
+ this.signature = cw.newUTF8(signature);
+ }
+ if (value != null) {
+ this.value = cw.newConstItem(value).index;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(final int typeRef,
+ final TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attr) {
+ attr.next = attrs;
+ attrs = attr;
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of this field.
+ *
+ * @return the size of this field.
+ */
+ int getSize() {
+ int size = 8;
+ if (value != 0) {
+ cw.newUTF8("ConstantValue");
+ size += 8;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ cw.newUTF8("Synthetic");
+ size += 6;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ cw.newUTF8("Deprecated");
+ size += 6;
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ cw.newUTF8("Signature");
+ size += 8;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ cw.newUTF8("RuntimeVisibleAnnotations");
+ size += 8 + anns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ cw.newUTF8("RuntimeInvisibleAnnotations");
+ size += 8 + ianns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + tanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + itanns.getSize();
+ }
+ if (attrs != null) {
+ size += attrs.getSize(cw, null, 0, -1, -1);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of this field into the given byte vector.
+ *
+ * @param out
+ * where the content of this field must be put.
+ */
+ void put(final ByteVector out) {
+ final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
+ int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
+ out.putShort(access & ~mask).putShort(name).putShort(desc);
+ int attributeCount = 0;
+ if (value != 0) {
+ ++attributeCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (value != 0) {
+ out.putShort(cw.newUTF8("ConstantValue"));
+ out.putInt(2).putShort(value);
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(cw.newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(cw.newUTF8("Deprecated")).putInt(0);
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ out.putShort(cw.newUTF8("Signature"));
+ out.putInt(2).putShort(signature);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (attrs != null) {
+ attrs.put(cw, null, 0, -1, -1, out);
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Frame.java b/src/main/java/org/objectweb/asm/Frame.java
new file mode 100644
index 00000000..1f6106f6
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Frame.java
@@ -0,0 +1,1462 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * Information about the input and output stack map frames of a basic block.
+ *
+ * @author Eric Bruneton
+ */
+final class Frame {
+
+ /*
+ * Frames are computed in a two steps process: during the visit of each
+ * instruction, the state of the frame at the end of current basic block is
+ * updated by simulating the action of the instruction on the previous state
+ * of this so called "output frame". In visitMaxs, a fix point algorithm is
+ * used to compute the "input frame" of each basic block, i.e. the stack map
+ * frame at the beginning of the basic block, starting from the input frame
+ * of the first basic block (which is computed from the method descriptor),
+ * and by using the previously computed output frames to compute the input
+ * state of the other blocks.
+ *
+ * All output and input frames are stored as arrays of integers. Reference
+ * and array types are represented by an index into a type table (which is
+ * not the same as the constant pool of the class, in order to avoid adding
+ * unnecessary constants in the pool - not all computed frames will end up
+ * being stored in the stack map table). This allows very fast type
+ * comparisons.
+ *
+ * Output stack map frames are computed relatively to the input frame of the
+ * basic block, which is not yet known when output frames are computed. It
+ * is therefore necessary to be able to represent abstract types such as
+ * "the type at position x in the input frame locals" or "the type at
+ * position x from the top of the input frame stack" or even "the type at
+ * position x in the input frame, with y more (or less) array dimensions".
+ * This explains the rather complicated type format used in output frames.
+ *
+ * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a
+ * signed number of array dimensions (from -8 to 7). KIND is either BASE,
+ * LOCAL or STACK. BASE is used for types that are not relative to the input
+ * frame. LOCAL is used for types that are relative to the input local
+ * variable types. STACK is used for types that are relative to the input
+ * stack types. VALUE depends on KIND. For LOCAL types, it is an index in
+ * the input local variable types. For STACK types, it is a position
+ * relatively to the top of input frame stack. For BASE types, it is either
+ * one of the constants defined below, or for OBJECT and UNINITIALIZED
+ * types, a tag and an index in the type table.
+ *
+ * Output frames can contain types of any kind and with a positive or
+ * negative dimension (and even unassigned types, represented by 0 - which
+ * does not correspond to any valid type value). Input frames can only
+ * contain BASE types of positive or null dimension. In all cases the type
+ * table contains only internal type names (array type descriptors are
+ * forbidden - dimensions must be represented through the DIM field).
+ *
+ * The LONG and DOUBLE types are always represented by using two slots (LONG
+ * + TOP or DOUBLE + TOP), for local variable types as well as in the
+ * operand stack. This is necessary to be able to simulate DUPx_y
+ * instructions, whose effect would be dependent on the actual type values
+ * if types were always represented by a single slot in the stack (and this
+ * is not possible, since actual type values are not always known - cf LOCAL
+ * and STACK type kinds).
+ */
+
+ /**
+ * Mask to get the dimension of a frame type. This dimension is a signed
+ * integer between -8 and 7.
+ */
+ static final int DIM = 0xF0000000;
+
+ /**
+ * Constant to be added to a type to get a type with one more dimension.
+ */
+ static final int ARRAY_OF = 0x10000000;
+
+ /**
+ * Constant to be added to a type to get a type with one less dimension.
+ */
+ static final int ELEMENT_OF = 0xF0000000;
+
+ /**
+ * Mask to get the kind of a frame type.
+ *
+ * @see #BASE
+ * @see #LOCAL
+ * @see #STACK
+ */
+ static final int KIND = 0xF000000;
+
+ /**
+ * Flag used for LOCAL and STACK types. Indicates that if this type happens
+ * to be a long or double type (during the computations of input frames),
+ * then it must be set to TOP because the second word of this value has been
+ * reused to store other data in the basic block. Hence the first word no
+ * longer stores a valid long or double value.
+ */
+ static final int TOP_IF_LONG_OR_DOUBLE = 0x800000;
+
+ /**
+ * Mask to get the value of a frame type.
+ */
+ static final int VALUE = 0x7FFFFF;
+
+ /**
+ * Mask to get the kind of base types.
+ */
+ static final int BASE_KIND = 0xFF00000;
+
+ /**
+ * Mask to get the value of base types.
+ */
+ static final int BASE_VALUE = 0xFFFFF;
+
+ /**
+ * Kind of the types that are not relative to an input stack map frame.
+ */
+ static final int BASE = 0x1000000;
+
+ /**
+ * Base kind of the base reference types. The BASE_VALUE of such types is an
+ * index into the type table.
+ */
+ static final int OBJECT = BASE | 0x700000;
+
+ /**
+ * Base kind of the uninitialized base types. The BASE_VALUE of such types
+ * in an index into the type table (the Item at that index contains both an
+ * instruction offset and an internal class name).
+ */
+ static final int UNINITIALIZED = BASE | 0x800000;
+
+ /**
+ * Kind of the types that are relative to the local variable types of an
+ * input stack map frame. The value of such types is a local variable index.
+ */
+ private static final int LOCAL = 0x2000000;
+
+ /**
+ * Kind of the the types that are relative to the stack of an input stack
+ * map frame. The value of such types is a position relatively to the top of
+ * this stack.
+ */
+ private static final int STACK = 0x3000000;
+
+ /**
+ * The TOP type. This is a BASE type.
+ */
+ static final int TOP = BASE | 0;
+
+ /**
+ * The BOOLEAN type. This is a BASE type mainly used for array types.
+ */
+ static final int BOOLEAN = BASE | 9;
+
+ /**
+ * The BYTE type. This is a BASE type mainly used for array types.
+ */
+ static final int BYTE = BASE | 10;
+
+ /**
+ * The CHAR type. This is a BASE type mainly used for array types.
+ */
+ static final int CHAR = BASE | 11;
+
+ /**
+ * The SHORT type. This is a BASE type mainly used for array types.
+ */
+ static final int SHORT = BASE | 12;
+
+ /**
+ * The INTEGER type. This is a BASE type.
+ */
+ static final int INTEGER = BASE | 1;
+
+ /**
+ * The FLOAT type. This is a BASE type.
+ */
+ static final int FLOAT = BASE | 2;
+
+ /**
+ * The DOUBLE type. This is a BASE type.
+ */
+ static final int DOUBLE = BASE | 3;
+
+ /**
+ * The LONG type. This is a BASE type.
+ */
+ static final int LONG = BASE | 4;
+
+ /**
+ * The NULL type. This is a BASE type.
+ */
+ static final int NULL = BASE | 5;
+
+ /**
+ * The UNINITIALIZED_THIS type. This is a BASE type.
+ */
+ static final int UNINITIALIZED_THIS = BASE | 6;
+
+ /**
+ * The stack size variation corresponding to each JVM instruction. This
+ * stack variation is equal to the size of the values produced by an
+ * instruction, minus the size of the values consumed by this instruction.
+ */
+ static final int[] SIZE;
+
+ /**
+ * Computes the stack size variation corresponding to each JVM instruction.
+ */
+ static {
+ int i;
+ int[] b = new int[202];
+ String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
+ + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
+ + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
+ + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
+ for (i = 0; i < b.length; ++i) {
+ b[i] = s.charAt(i) - 'E';
+ }
+ SIZE = b;
+
+ // code to generate the above string
+ //
+ // int NA = 0; // not applicable (unused opcode or variable size opcode)
+ //
+ // b = new int[] {
+ // 0, //NOP, // visitInsn
+ // 1, //ACONST_NULL, // -
+ // 1, //ICONST_M1, // -
+ // 1, //ICONST_0, // -
+ // 1, //ICONST_1, // -
+ // 1, //ICONST_2, // -
+ // 1, //ICONST_3, // -
+ // 1, //ICONST_4, // -
+ // 1, //ICONST_5, // -
+ // 2, //LCONST_0, // -
+ // 2, //LCONST_1, // -
+ // 1, //FCONST_0, // -
+ // 1, //FCONST_1, // -
+ // 1, //FCONST_2, // -
+ // 2, //DCONST_0, // -
+ // 2, //DCONST_1, // -
+ // 1, //BIPUSH, // visitIntInsn
+ // 1, //SIPUSH, // -
+ // 1, //LDC, // visitLdcInsn
+ // NA, //LDC_W, // -
+ // NA, //LDC2_W, // -
+ // 1, //ILOAD, // visitVarInsn
+ // 2, //LLOAD, // -
+ // 1, //FLOAD, // -
+ // 2, //DLOAD, // -
+ // 1, //ALOAD, // -
+ // NA, //ILOAD_0, // -
+ // NA, //ILOAD_1, // -
+ // NA, //ILOAD_2, // -
+ // NA, //ILOAD_3, // -
+ // NA, //LLOAD_0, // -
+ // NA, //LLOAD_1, // -
+ // NA, //LLOAD_2, // -
+ // NA, //LLOAD_3, // -
+ // NA, //FLOAD_0, // -
+ // NA, //FLOAD_1, // -
+ // NA, //FLOAD_2, // -
+ // NA, //FLOAD_3, // -
+ // NA, //DLOAD_0, // -
+ // NA, //DLOAD_1, // -
+ // NA, //DLOAD_2, // -
+ // NA, //DLOAD_3, // -
+ // NA, //ALOAD_0, // -
+ // NA, //ALOAD_1, // -
+ // NA, //ALOAD_2, // -
+ // NA, //ALOAD_3, // -
+ // -1, //IALOAD, // visitInsn
+ // 0, //LALOAD, // -
+ // -1, //FALOAD, // -
+ // 0, //DALOAD, // -
+ // -1, //AALOAD, // -
+ // -1, //BALOAD, // -
+ // -1, //CALOAD, // -
+ // -1, //SALOAD, // -
+ // -1, //ISTORE, // visitVarInsn
+ // -2, //LSTORE, // -
+ // -1, //FSTORE, // -
+ // -2, //DSTORE, // -
+ // -1, //ASTORE, // -
+ // NA, //ISTORE_0, // -
+ // NA, //ISTORE_1, // -
+ // NA, //ISTORE_2, // -
+ // NA, //ISTORE_3, // -
+ // NA, //LSTORE_0, // -
+ // NA, //LSTORE_1, // -
+ // NA, //LSTORE_2, // -
+ // NA, //LSTORE_3, // -
+ // NA, //FSTORE_0, // -
+ // NA, //FSTORE_1, // -
+ // NA, //FSTORE_2, // -
+ // NA, //FSTORE_3, // -
+ // NA, //DSTORE_0, // -
+ // NA, //DSTORE_1, // -
+ // NA, //DSTORE_2, // -
+ // NA, //DSTORE_3, // -
+ // NA, //ASTORE_0, // -
+ // NA, //ASTORE_1, // -
+ // NA, //ASTORE_2, // -
+ // NA, //ASTORE_3, // -
+ // -3, //IASTORE, // visitInsn
+ // -4, //LASTORE, // -
+ // -3, //FASTORE, // -
+ // -4, //DASTORE, // -
+ // -3, //AASTORE, // -
+ // -3, //BASTORE, // -
+ // -3, //CASTORE, // -
+ // -3, //SASTORE, // -
+ // -1, //POP, // -
+ // -2, //POP2, // -
+ // 1, //DUP, // -
+ // 1, //DUP_X1, // -
+ // 1, //DUP_X2, // -
+ // 2, //DUP2, // -
+ // 2, //DUP2_X1, // -
+ // 2, //DUP2_X2, // -
+ // 0, //SWAP, // -
+ // -1, //IADD, // -
+ // -2, //LADD, // -
+ // -1, //FADD, // -
+ // -2, //DADD, // -
+ // -1, //ISUB, // -
+ // -2, //LSUB, // -
+ // -1, //FSUB, // -
+ // -2, //DSUB, // -
+ // -1, //IMUL, // -
+ // -2, //LMUL, // -
+ // -1, //FMUL, // -
+ // -2, //DMUL, // -
+ // -1, //IDIV, // -
+ // -2, //LDIV, // -
+ // -1, //FDIV, // -
+ // -2, //DDIV, // -
+ // -1, //IREM, // -
+ // -2, //LREM, // -
+ // -1, //FREM, // -
+ // -2, //DREM, // -
+ // 0, //INEG, // -
+ // 0, //LNEG, // -
+ // 0, //FNEG, // -
+ // 0, //DNEG, // -
+ // -1, //ISHL, // -
+ // -1, //LSHL, // -
+ // -1, //ISHR, // -
+ // -1, //LSHR, // -
+ // -1, //IUSHR, // -
+ // -1, //LUSHR, // -
+ // -1, //IAND, // -
+ // -2, //LAND, // -
+ // -1, //IOR, // -
+ // -2, //LOR, // -
+ // -1, //IXOR, // -
+ // -2, //LXOR, // -
+ // 0, //IINC, // visitIincInsn
+ // 1, //I2L, // visitInsn
+ // 0, //I2F, // -
+ // 1, //I2D, // -
+ // -1, //L2I, // -
+ // -1, //L2F, // -
+ // 0, //L2D, // -
+ // 0, //F2I, // -
+ // 1, //F2L, // -
+ // 1, //F2D, // -
+ // -1, //D2I, // -
+ // 0, //D2L, // -
+ // -1, //D2F, // -
+ // 0, //I2B, // -
+ // 0, //I2C, // -
+ // 0, //I2S, // -
+ // -3, //LCMP, // -
+ // -1, //FCMPL, // -
+ // -1, //FCMPG, // -
+ // -3, //DCMPL, // -
+ // -3, //DCMPG, // -
+ // -1, //IFEQ, // visitJumpInsn
+ // -1, //IFNE, // -
+ // -1, //IFLT, // -
+ // -1, //IFGE, // -
+ // -1, //IFGT, // -
+ // -1, //IFLE, // -
+ // -2, //IF_ICMPEQ, // -
+ // -2, //IF_ICMPNE, // -
+ // -2, //IF_ICMPLT, // -
+ // -2, //IF_ICMPGE, // -
+ // -2, //IF_ICMPGT, // -
+ // -2, //IF_ICMPLE, // -
+ // -2, //IF_ACMPEQ, // -
+ // -2, //IF_ACMPNE, // -
+ // 0, //GOTO, // -
+ // 1, //JSR, // -
+ // 0, //RET, // visitVarInsn
+ // -1, //TABLESWITCH, // visiTableSwitchInsn
+ // -1, //LOOKUPSWITCH, // visitLookupSwitch
+ // -1, //IRETURN, // visitInsn
+ // -2, //LRETURN, // -
+ // -1, //FRETURN, // -
+ // -2, //DRETURN, // -
+ // -1, //ARETURN, // -
+ // 0, //RETURN, // -
+ // NA, //GETSTATIC, // visitFieldInsn
+ // NA, //PUTSTATIC, // -
+ // NA, //GETFIELD, // -
+ // NA, //PUTFIELD, // -
+ // NA, //INVOKEVIRTUAL, // visitMethodInsn
+ // NA, //INVOKESPECIAL, // -
+ // NA, //INVOKESTATIC, // -
+ // NA, //INVOKEINTERFACE, // -
+ // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn
+ // 1, //NEW, // visitTypeInsn
+ // 0, //NEWARRAY, // visitIntInsn
+ // 0, //ANEWARRAY, // visitTypeInsn
+ // 0, //ARRAYLENGTH, // visitInsn
+ // NA, //ATHROW, // -
+ // 0, //CHECKCAST, // visitTypeInsn
+ // 0, //INSTANCEOF, // -
+ // -1, //MONITORENTER, // visitInsn
+ // -1, //MONITOREXIT, // -
+ // NA, //WIDE, // NOT VISITED
+ // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn
+ // -1, //IFNULL, // visitJumpInsn
+ // -1, //IFNONNULL, // -
+ // NA, //GOTO_W, // -
+ // NA, //JSR_W, // -
+ // };
+ // for (i = 0; i < b.length; ++i) {
+ // System.err.print((char)('E' + b[i]));
+ // }
+ // System.err.println();
+ }
+
+ /**
+ * The label (i.e. basic block) to which these input and output stack map
+ * frames correspond.
+ */
+ Label owner;
+
+ /**
+ * The input stack map frame locals.
+ */
+ int[] inputLocals;
+
+ /**
+ * The input stack map frame stack.
+ */
+ int[] inputStack;
+
+ /**
+ * The output stack map frame locals.
+ */
+ private int[] outputLocals;
+
+ /**
+ * The output stack map frame stack.
+ */
+ private int[] outputStack;
+
+ /**
+ * Relative size of the output stack. The exact semantics of this field
+ * depends on the algorithm that is used.
+ *
+ * When only the maximum stack size is computed, this field is the size of
+ * the output stack relatively to the top of the input stack.
+ *
+ * When the stack map frames are completely computed, this field is the
+ * actual number of types in {@link #outputStack}.
+ */
+ private int outputStackTop;
+
+ /**
+ * Number of types that are initialized in the basic block.
+ *
+ * @see #initializations
+ */
+ private int initializationCount;
+
+ /**
+ * The types that are initialized in the basic block. A constructor
+ * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace
+ * every occurence of this type in the local variables and in the
+ * operand stack. This cannot be done during the first phase of the
+ * algorithm since, during this phase, the local variables and the operand
+ * stack are not completely computed. It is therefore necessary to store the
+ * types on which constructors are invoked in the basic block, in order to
+ * do this replacement during the second phase of the algorithm, where the
+ * frames are fully computed. Note that this array can contain types that
+ * are relative to input locals or to the input stack (see below for the
+ * description of the algorithm).
+ */
+ private int[] initializations;
+
+ /**
+ * Returns the output frame local variable type at the given index.
+ *
+ * @param local
+ * the index of the local that must be returned.
+ * @return the output frame local variable type at the given index.
+ */
+ private int get(final int local) {
+ if (outputLocals == null || local >= outputLocals.length) {
+ // this local has never been assigned in this basic block,
+ // so it is still equal to its value in the input frame
+ return LOCAL | local;
+ } else {
+ int type = outputLocals[local];
+ if (type == 0) {
+ // this local has never been assigned in this basic block,
+ // so it is still equal to its value in the input frame
+ type = outputLocals[local] = LOCAL | local;
+ }
+ return type;
+ }
+ }
+
+ /**
+ * Sets the output frame local variable type at the given index.
+ *
+ * @param local
+ * the index of the local that must be set.
+ * @param type
+ * the value of the local that must be set.
+ */
+ private void set(final int local, final int type) {
+ // creates and/or resizes the output local variables array if necessary
+ if (outputLocals == null) {
+ outputLocals = new int[10];
+ }
+ int n = outputLocals.length;
+ if (local >= n) {
+ int[] t = new int[Math.max(local + 1, 2 * n)];
+ System.arraycopy(outputLocals, 0, t, 0, n);
+ outputLocals = t;
+ }
+ // sets the local variable
+ outputLocals[local] = type;
+ }
+
+ /**
+ * Pushes a new type onto the output frame stack.
+ *
+ * @param type
+ * the type that must be pushed.
+ */
+ private void push(final int type) {
+ // creates and/or resizes the output stack array if necessary
+ if (outputStack == null) {
+ outputStack = new int[10];
+ }
+ int n = outputStack.length;
+ if (outputStackTop >= n) {
+ int[] t = new int[Math.max(outputStackTop + 1, 2 * n)];
+ System.arraycopy(outputStack, 0, t, 0, n);
+ outputStack = t;
+ }
+ // pushes the type on the output stack
+ outputStack[outputStackTop++] = type;
+ // updates the maximun height reached by the output stack, if needed
+ int top = owner.inputStackTop + outputStackTop;
+ if (top > owner.outputStackMax) {
+ owner.outputStackMax = top;
+ }
+ }
+
+ /**
+ * Pushes a new type onto the output frame stack.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param desc
+ * the descriptor of the type to be pushed. Can also be a method
+ * descriptor (in this case this method pushes its return type
+ * onto the output frame stack).
+ */
+ private void push(final ClassWriter cw, final String desc) {
+ int type = type(cw, desc);
+ if (type != 0) {
+ push(type);
+ if (type == LONG || type == DOUBLE) {
+ push(TOP);
+ }
+ }
+ }
+
+ /**
+ * Returns the int encoding of the given type.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param desc
+ * a type descriptor.
+ * @return the int encoding of the given type.
+ */
+ private static int type(final ClassWriter cw, final String desc) {
+ String t;
+ int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0;
+ switch (desc.charAt(index)) {
+ case 'V':
+ return 0;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ return INTEGER;
+ case 'F':
+ return FLOAT;
+ case 'J':
+ return LONG;
+ case 'D':
+ return DOUBLE;
+ case 'L':
+ // stores the internal name, not the descriptor!
+ t = desc.substring(index + 1, desc.length() - 1);
+ return OBJECT | cw.addType(t);
+ // case '[':
+ default:
+ // extracts the dimensions and the element type
+ int data;
+ int dims = index + 1;
+ while (desc.charAt(dims) == '[') {
+ ++dims;
+ }
+ switch (desc.charAt(dims)) {
+ case 'Z':
+ data = BOOLEAN;
+ break;
+ case 'C':
+ data = CHAR;
+ break;
+ case 'B':
+ data = BYTE;
+ break;
+ case 'S':
+ data = SHORT;
+ break;
+ case 'I':
+ data = INTEGER;
+ break;
+ case 'F':
+ data = FLOAT;
+ break;
+ case 'J':
+ data = LONG;
+ break;
+ case 'D':
+ data = DOUBLE;
+ break;
+ // case 'L':
+ default:
+ // stores the internal name, not the descriptor
+ t = desc.substring(dims + 1, desc.length() - 1);
+ data = OBJECT | cw.addType(t);
+ }
+ return (dims - index) << 28 | data;
+ }
+ }
+
+ /**
+ * Pops a type from the output frame stack and returns its value.
+ *
+ * @return the type that has been popped from the output frame stack.
+ */
+ private int pop() {
+ if (outputStackTop > 0) {
+ return outputStack[--outputStackTop];
+ } else {
+ // if the output frame stack is empty, pops from the input stack
+ return STACK | -(--owner.inputStackTop);
+ }
+ }
+
+ /**
+ * Pops the given number of types from the output frame stack.
+ *
+ * @param elements
+ * the number of types that must be popped.
+ */
+ private void pop(final int elements) {
+ if (outputStackTop >= elements) {
+ outputStackTop -= elements;
+ } else {
+ // if the number of elements to be popped is greater than the number
+ // of elements in the output stack, clear it, and pops the remaining
+ // elements from the input stack.
+ owner.inputStackTop -= elements - outputStackTop;
+ outputStackTop = 0;
+ }
+ }
+
+ /**
+ * Pops a type from the output frame stack.
+ *
+ * @param desc
+ * the descriptor of the type to be popped. Can also be a method
+ * descriptor (in this case this method pops the types
+ * corresponding to the method arguments).
+ */
+ private void pop(final String desc) {
+ char c = desc.charAt(0);
+ if (c == '(') {
+ pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1);
+ } else if (c == 'J' || c == 'D') {
+ pop(2);
+ } else {
+ pop(1);
+ }
+ }
+
+ /**
+ * Adds a new type to the list of types on which a constructor is invoked in
+ * the basic block.
+ *
+ * @param var
+ * a type on a which a constructor is invoked.
+ */
+ private void init(final int var) {
+ // creates and/or resizes the initializations array if necessary
+ if (initializations == null) {
+ initializations = new int[2];
+ }
+ int n = initializations.length;
+ if (initializationCount >= n) {
+ int[] t = new int[Math.max(initializationCount + 1, 2 * n)];
+ System.arraycopy(initializations, 0, t, 0, n);
+ initializations = t;
+ }
+ // stores the type to be initialized
+ initializations[initializationCount++] = var;
+ }
+
+ /**
+ * Replaces the given type with the appropriate type if it is one of the
+ * types on which a constructor is invoked in the basic block.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param t
+ * a type
+ * @return t or, if t is one of the types on which a constructor is invoked
+ * in the basic block, the type corresponding to this constructor.
+ */
+ private int init(final ClassWriter cw, final int t) {
+ int s;
+ if (t == UNINITIALIZED_THIS) {
+ s = OBJECT | cw.addType(cw.thisName);
+ } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) {
+ String type = cw.typeTable[t & BASE_VALUE].strVal1;
+ s = OBJECT | cw.addType(type);
+ } else {
+ return t;
+ }
+ for (int j = 0; j < initializationCount; ++j) {
+ int u = initializations[j];
+ int dim = u & DIM;
+ int kind = u & KIND;
+ if (kind == LOCAL) {
+ u = dim + inputLocals[u & VALUE];
+ } else if (kind == STACK) {
+ u = dim + inputStack[inputStack.length - (u & VALUE)];
+ }
+ if (t == u) {
+ return s;
+ }
+ }
+ return t;
+ }
+
+ /**
+ * Initializes the input frame of the first basic block from the method
+ * descriptor.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param access
+ * the access flags of the method to which this label belongs.
+ * @param args
+ * the formal parameter types of this method.
+ * @param maxLocals
+ * the maximum number of local variables of this method.
+ */
+ void initInputFrame(final ClassWriter cw, final int access,
+ final Type[] args, final int maxLocals) {
+ inputLocals = new int[maxLocals];
+ inputStack = new int[0];
+ int i = 0;
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) {
+ inputLocals[i++] = OBJECT | cw.addType(cw.thisName);
+ } else {
+ inputLocals[i++] = UNINITIALIZED_THIS;
+ }
+ }
+ for (int j = 0; j < args.length; ++j) {
+ int t = type(cw, args[j].getDescriptor());
+ inputLocals[i++] = t;
+ if (t == LONG || t == DOUBLE) {
+ inputLocals[i++] = TOP;
+ }
+ }
+ while (i < maxLocals) {
+ inputLocals[i++] = TOP;
+ }
+ }
+
+ /**
+ * Simulates the action of the given instruction on the output stack frame.
+ *
+ * @param opcode
+ * the opcode of the instruction.
+ * @param arg
+ * the operand of the instruction, if any.
+ * @param cw
+ * the class writer to which this label belongs.
+ * @param item
+ * the operand of the instructions, if any.
+ */
+ void execute(final int opcode, final int arg, final ClassWriter cw,
+ final Item item) {
+ int t1, t2, t3, t4;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.GOTO:
+ case Opcodes.RETURN:
+ break;
+ case Opcodes.ACONST_NULL:
+ push(NULL);
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH:
+ case Opcodes.ILOAD:
+ push(INTEGER);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.LLOAD:
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.FLOAD:
+ push(FLOAT);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.DLOAD:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LDC:
+ switch (item.type) {
+ case ClassWriter.INT:
+ push(INTEGER);
+ break;
+ case ClassWriter.LONG:
+ push(LONG);
+ push(TOP);
+ break;
+ case ClassWriter.FLOAT:
+ push(FLOAT);
+ break;
+ case ClassWriter.DOUBLE:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case ClassWriter.CLASS:
+ push(OBJECT | cw.addType("java/lang/Class"));
+ break;
+ case ClassWriter.STR:
+ push(OBJECT | cw.addType("java/lang/String"));
+ break;
+ case ClassWriter.MTYPE:
+ push(OBJECT | cw.addType("java/lang/invoke/MethodType"));
+ break;
+ // case ClassWriter.HANDLE_BASE + [1..9]:
+ default:
+ push(OBJECT | cw.addType("java/lang/invoke/MethodHandle"));
+ }
+ break;
+ case Opcodes.ALOAD:
+ push(get(arg));
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.D2L:
+ pop(2);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FALOAD:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DALOAD:
+ case Opcodes.L2D:
+ pop(2);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.AALOAD:
+ pop(1);
+ t1 = pop();
+ push(ELEMENT_OF + t1);
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ t1 = pop();
+ set(arg, t1);
+ if (arg > 0) {
+ t2 = get(arg - 1);
+ // if t2 is of kind STACK or LOCAL we cannot know its size!
+ if (t2 == LONG || t2 == DOUBLE) {
+ set(arg - 1, TOP);
+ } else if ((t2 & KIND) != BASE) {
+ set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
+ }
+ }
+ break;
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ pop(1);
+ t1 = pop();
+ set(arg, t1);
+ set(arg + 1, TOP);
+ if (arg > 0) {
+ t2 = get(arg - 1);
+ // if t2 is of kind STACK or LOCAL we cannot know its size!
+ if (t2 == LONG || t2 == DOUBLE) {
+ set(arg - 1, TOP);
+ } else if ((t2 & KIND) != BASE) {
+ set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
+ }
+ }
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.AASTORE:
+ pop(3);
+ break;
+ case Opcodes.LASTORE:
+ case Opcodes.DASTORE:
+ pop(4);
+ break;
+ case Opcodes.POP:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.TABLESWITCH:
+ case Opcodes.LOOKUPSWITCH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ pop(1);
+ break;
+ case Opcodes.POP2:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ pop(2);
+ break;
+ case Opcodes.DUP:
+ t1 = pop();
+ push(t1);
+ push(t1);
+ break;
+ case Opcodes.DUP_X1:
+ t1 = pop();
+ t2 = pop();
+ push(t1);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP_X2:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ push(t1);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2:
+ t1 = pop();
+ t2 = pop();
+ push(t2);
+ push(t1);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2_X1:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ push(t2);
+ push(t1);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2_X2:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ t4 = pop();
+ push(t2);
+ push(t1);
+ push(t4);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.SWAP:
+ t1 = pop();
+ t2 = pop();
+ push(t1);
+ push(t2);
+ break;
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.IAND:
+ case Opcodes.IOR:
+ case Opcodes.IXOR:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ case Opcodes.L2I:
+ case Opcodes.D2I:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LAND:
+ case Opcodes.LOR:
+ case Opcodes.LXOR:
+ pop(4);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.L2F:
+ case Opcodes.D2F:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ pop(4);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ pop(3);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.IINC:
+ set(arg, INTEGER);
+ break;
+ case Opcodes.I2L:
+ case Opcodes.F2L:
+ pop(1);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.I2F:
+ pop(1);
+ push(FLOAT);
+ break;
+ case Opcodes.I2D:
+ case Opcodes.F2D:
+ pop(1);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.F2I:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.INSTANCEOF:
+ pop(1);
+ push(INTEGER);
+ break;
+ case Opcodes.LCMP:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ pop(4);
+ push(INTEGER);
+ break;
+ case Opcodes.JSR:
+ case Opcodes.RET:
+ throw new RuntimeException(
+ "JSR/RET are not supported with computeFrames option");
+ case Opcodes.GETSTATIC:
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.PUTSTATIC:
+ pop(item.strVal3);
+ break;
+ case Opcodes.GETFIELD:
+ pop(1);
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.PUTFIELD:
+ pop(item.strVal3);
+ pop();
+ break;
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ pop(item.strVal3);
+ if (opcode != Opcodes.INVOKESTATIC) {
+ t1 = pop();
+ if (opcode == Opcodes.INVOKESPECIAL
+ && item.strVal2.charAt(0) == '<') {
+ init(t1);
+ }
+ }
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.INVOKEDYNAMIC:
+ pop(item.strVal2);
+ push(cw, item.strVal2);
+ break;
+ case Opcodes.NEW:
+ push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg));
+ break;
+ case Opcodes.NEWARRAY:
+ pop();
+ switch (arg) {
+ case Opcodes.T_BOOLEAN:
+ push(ARRAY_OF | BOOLEAN);
+ break;
+ case Opcodes.T_CHAR:
+ push(ARRAY_OF | CHAR);
+ break;
+ case Opcodes.T_BYTE:
+ push(ARRAY_OF | BYTE);
+ break;
+ case Opcodes.T_SHORT:
+ push(ARRAY_OF | SHORT);
+ break;
+ case Opcodes.T_INT:
+ push(ARRAY_OF | INTEGER);
+ break;
+ case Opcodes.T_FLOAT:
+ push(ARRAY_OF | FLOAT);
+ break;
+ case Opcodes.T_DOUBLE:
+ push(ARRAY_OF | DOUBLE);
+ break;
+ // case Opcodes.T_LONG:
+ default:
+ push(ARRAY_OF | LONG);
+ break;
+ }
+ break;
+ case Opcodes.ANEWARRAY:
+ String s = item.strVal1;
+ pop();
+ if (s.charAt(0) == '[') {
+ push(cw, '[' + s);
+ } else {
+ push(ARRAY_OF | OBJECT | cw.addType(s));
+ }
+ break;
+ case Opcodes.CHECKCAST:
+ s = item.strVal1;
+ pop();
+ if (s.charAt(0) == '[') {
+ push(cw, s);
+ } else {
+ push(OBJECT | cw.addType(s));
+ }
+ break;
+ // case Opcodes.MULTIANEWARRAY:
+ default:
+ pop(arg);
+ push(cw, item.strVal1);
+ break;
+ }
+ }
+
+ /**
+ * Merges the input frame of the given basic block with the input and output
+ * frames of this basic block. Returns true if the input frame of
+ * the given label has been changed by this operation.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param frame
+ * the basic block whose input frame must be updated.
+ * @param edge
+ * the kind of the {@link Edge} between this label and 'label'.
+ * See {@link Edge#info}.
+ * @return true if the input frame of the given label has been
+ * changed by this operation.
+ */
+ boolean merge(final ClassWriter cw, final Frame frame, final int edge) {
+ boolean changed = false;
+ int i, s, dim, kind, t;
+
+ int nLocal = inputLocals.length;
+ int nStack = inputStack.length;
+ if (frame.inputLocals == null) {
+ frame.inputLocals = new int[nLocal];
+ changed = true;
+ }
+
+ for (i = 0; i < nLocal; ++i) {
+ if (outputLocals != null && i < outputLocals.length) {
+ s = outputLocals[i];
+ if (s == 0) {
+ t = inputLocals[i];
+ } else {
+ dim = s & DIM;
+ kind = s & KIND;
+ if (kind == BASE) {
+ t = s;
+ } else {
+ if (kind == LOCAL) {
+ t = dim + inputLocals[s & VALUE];
+ } else {
+ t = dim + inputStack[nStack - (s & VALUE)];
+ }
+ if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
+ && (t == LONG || t == DOUBLE)) {
+ t = TOP;
+ }
+ }
+ }
+ } else {
+ t = inputLocals[i];
+ }
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputLocals, i);
+ }
+
+ if (edge > 0) {
+ for (i = 0; i < nLocal; ++i) {
+ t = inputLocals[i];
+ changed |= merge(cw, t, frame.inputLocals, i);
+ }
+ if (frame.inputStack == null) {
+ frame.inputStack = new int[1];
+ changed = true;
+ }
+ changed |= merge(cw, edge, frame.inputStack, 0);
+ return changed;
+ }
+
+ int nInputStack = inputStack.length + owner.inputStackTop;
+ if (frame.inputStack == null) {
+ frame.inputStack = new int[nInputStack + outputStackTop];
+ changed = true;
+ }
+
+ for (i = 0; i < nInputStack; ++i) {
+ t = inputStack[i];
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputStack, i);
+ }
+ for (i = 0; i < outputStackTop; ++i) {
+ s = outputStack[i];
+ dim = s & DIM;
+ kind = s & KIND;
+ if (kind == BASE) {
+ t = s;
+ } else {
+ if (kind == LOCAL) {
+ t = dim + inputLocals[s & VALUE];
+ } else {
+ t = dim + inputStack[nStack - (s & VALUE)];
+ }
+ if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
+ && (t == LONG || t == DOUBLE)) {
+ t = TOP;
+ }
+ }
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputStack, nInputStack + i);
+ }
+ return changed;
+ }
+
+ /**
+ * Merges the type at the given index in the given type array with the given
+ * type. Returns true if the type array has been modified by this
+ * operation.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param t
+ * the type with which the type array element must be merged.
+ * @param types
+ * an array of types.
+ * @param index
+ * the index of the type that must be merged in 'types'.
+ * @return true if the type array has been modified by this
+ * operation.
+ */
+ private static boolean merge(final ClassWriter cw, int t,
+ final int[] types, final int index) {
+ int u = types[index];
+ if (u == t) {
+ // if the types are equal, merge(u,t)=u, so there is no change
+ return false;
+ }
+ if ((t & ~DIM) == NULL) {
+ if (u == NULL) {
+ return false;
+ }
+ t = NULL;
+ }
+ if (u == 0) {
+ // if types[index] has never been assigned, merge(u,t)=t
+ types[index] = t;
+ return true;
+ }
+ int v;
+ if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) {
+ // if u is a reference type of any dimension
+ if (t == NULL) {
+ // if t is the NULL type, merge(u,t)=u, so there is no change
+ return false;
+ } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) {
+ // if t and u have the same dimension and same base kind
+ if ((u & BASE_KIND) == OBJECT) {
+ // if t is also a reference type, and if u and t have the
+ // same dimension merge(u,t) = dim(t) | common parent of the
+ // element types of u and t
+ v = (t & DIM) | OBJECT
+ | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE);
+ } else {
+ // if u and t are array types, but not with the same element
+ // type, merge(u,t) = dim(u) - 1 | java/lang/Object
+ int vdim = ELEMENT_OF + (u & DIM);
+ v = vdim | OBJECT | cw.addType("java/lang/Object");
+ }
+ } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) {
+ // if t is any other reference or array type, the merged type
+ // is min(udim, tdim) | java/lang/Object, where udim is the
+ // array dimension of u, minus 1 if u is an array type with a
+ // primitive element type (and similarly for tdim).
+ int tdim = (((t & DIM) == 0 || (t & BASE_KIND) == OBJECT) ? 0
+ : ELEMENT_OF) + (t & DIM);
+ int udim = (((u & DIM) == 0 || (u & BASE_KIND) == OBJECT) ? 0
+ : ELEMENT_OF) + (u & DIM);
+ v = Math.min(tdim, udim) | OBJECT
+ | cw.addType("java/lang/Object");
+ } else {
+ // if t is any other type, merge(u,t)=TOP
+ v = TOP;
+ }
+ } else if (u == NULL) {
+ // if u is the NULL type, merge(u,t)=t,
+ // or TOP if t is not a reference type
+ v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP;
+ } else {
+ // if u is any other type, merge(u,t)=TOP whatever t
+ v = TOP;
+ }
+ if (u != v) {
+ types[index] = v;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Handle.java b/src/main/java/org/objectweb/asm/Handle.java
new file mode 100644
index 00000000..a6279114
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Handle.java
@@ -0,0 +1,170 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.objectweb.asm;
+
+/**
+ * A reference to a field or a method.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public final class Handle {
+
+ /**
+ * The kind of field or method designated by this Handle. Should be
+ * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ final int tag;
+
+ /**
+ * The internal name of the class that owns the field or method designated
+ * by this handle.
+ */
+ final String owner;
+
+ /**
+ * The name of the field or method designated by this handle.
+ */
+ final String name;
+
+ /**
+ * The descriptor of the field or method designated by this handle.
+ */
+ final String desc;
+
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag
+ * the kind of field or method designated by this Handle. Must be
+ * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the class that owns the field or method
+ * designated by this handle.
+ * @param name
+ * the name of the field or method designated by this handle.
+ * @param desc
+ * the descriptor of the field or method designated by this
+ * handle.
+ */
+ public Handle(int tag, String owner, String name, String desc) {
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.desc = desc;
+ }
+
+ /**
+ * Returns the kind of field or method designated by this handle.
+ *
+ * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns the internal name of the class that owns the field or method
+ * designated by this handle.
+ *
+ * @return the internal name of the class that owns the field or method
+ * designated by this handle.
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the name of the field or method designated by this handle.
+ *
+ * @return the name of the field or method designated by this handle.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the descriptor of the field or method designated by this handle.
+ *
+ * @return the descriptor of the field or method designated by this handle.
+ */
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Handle)) {
+ return false;
+ }
+ Handle h = (Handle) obj;
+ return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
+ && desc.equals(h.desc);
+ }
+
+ @Override
+ public int hashCode() {
+ return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
+ }
+
+ /**
+ * Returns the textual representation of this handle. The textual
+ * representation is:
+ *
+ *
+ * owner '.' name desc ' ' '(' tag ')'
+ *
+ *
+ * . As this format is unambiguous, it can be parsed if necessary.
+ */
+ @Override
+ public String toString() {
+ return owner + '.' + name + desc + " (" + tag + ')';
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Handler.java b/src/main/java/org/objectweb/asm/Handler.java
new file mode 100644
index 00000000..b24591d8
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Handler.java
@@ -0,0 +1,121 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * Information about an exception handler block.
+ *
+ * @author Eric Bruneton
+ */
+class Handler {
+
+ /**
+ * Beginning of the exception handler's scope (inclusive).
+ */
+ Label start;
+
+ /**
+ * End of the exception handler's scope (exclusive).
+ */
+ Label end;
+
+ /**
+ * Beginning of the exception handler's code.
+ */
+ Label handler;
+
+ /**
+ * Internal name of the type of exceptions handled by this handler, or
+ * null to catch any exceptions.
+ */
+ String desc;
+
+ /**
+ * Constant pool index of the internal name of the type of exceptions
+ * handled by this handler, or 0 to catch any exceptions.
+ */
+ int type;
+
+ /**
+ * Next exception handler block info.
+ */
+ Handler next;
+
+ /**
+ * Removes the range between start and end from the given exception
+ * handlers.
+ *
+ * @param h
+ * an exception handler list.
+ * @param start
+ * the start of the range to be removed.
+ * @param end
+ * the end of the range to be removed. Maybe null.
+ * @return the exception handler list with the start-end range removed.
+ */
+ static Handler remove(Handler h, Label start, Label end) {
+ if (h == null) {
+ return null;
+ } else {
+ h.next = remove(h.next, start, end);
+ }
+ int hstart = h.start.position;
+ int hend = h.end.position;
+ int s = start.position;
+ int e = end == null ? Integer.MAX_VALUE : end.position;
+ // if [hstart,hend[ and [s,e[ intervals intersect...
+ if (s < hend && e > hstart) {
+ if (s <= hstart) {
+ if (e >= hend) {
+ // [hstart,hend[ fully included in [s,e[, h removed
+ h = h.next;
+ } else {
+ // [hstart,hend[ minus [s,e[ = [e,hend[
+ h.start = end;
+ }
+ } else if (e >= hend) {
+ // [hstart,hend[ minus [s,e[ = [hstart,s[
+ h.end = start;
+ } else {
+ // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[
+ Handler g = new Handler();
+ g.start = end;
+ g.end = h.end;
+ g.handler = h.handler;
+ g.desc = h.desc;
+ g.type = h.type;
+ g.next = h.next;
+ h.end = start;
+ h.next = g;
+ }
+ }
+ return h;
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Item.java b/src/main/java/org/objectweb/asm/Item.java
new file mode 100644
index 00000000..917524dd
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Item.java
@@ -0,0 +1,313 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A constant pool item. Constant pool items can be created with the 'newXXX'
+ * methods in the {@link ClassWriter} class.
+ *
+ * @author Eric Bruneton
+ */
+final class Item {
+
+ /**
+ * Index of this item in the constant pool.
+ */
+ int index;
+
+ /**
+ * Type of this constant pool item. A single class is used to represent all
+ * constant pool item types, in order to minimize the bytecode size of this
+ * package. The value of this field is one of {@link ClassWriter#INT},
+ * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT},
+ * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8},
+ * {@link ClassWriter#STR}, {@link ClassWriter#CLASS},
+ * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD},
+ * {@link ClassWriter#METH}, {@link ClassWriter#IMETH},
+ * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}.
+ *
+ * MethodHandle constant 9 variations are stored using a range of 9 values
+ * from {@link ClassWriter#HANDLE_BASE} + 1 to
+ * {@link ClassWriter#HANDLE_BASE} + 9.
+ *
+ * Special Item types are used for Items that are stored in the ClassWriter
+ * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
+ * avoid clashes with normal constant pool items in the ClassWriter constant
+ * pool's hash table. These special item types are
+ * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and
+ * {@link ClassWriter#TYPE_MERGED}.
+ */
+ int type;
+
+ /**
+ * Value of this item, for an integer item.
+ */
+ int intVal;
+
+ /**
+ * Value of this item, for a long item.
+ */
+ long longVal;
+
+ /**
+ * First part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal1;
+
+ /**
+ * Second part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal2;
+
+ /**
+ * Third part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal3;
+
+ /**
+ * The hash code value of this constant pool item.
+ */
+ int hashCode;
+
+ /**
+ * Link to another constant pool item, used for collision lists in the
+ * constant pool's hash table.
+ */
+ Item next;
+
+ /**
+ * Constructs an uninitialized {@link Item}.
+ */
+ Item() {
+ }
+
+ /**
+ * Constructs an uninitialized {@link Item} for constant pool element at
+ * given position.
+ *
+ * @param index
+ * index of the item to be constructed.
+ */
+ Item(final int index) {
+ this.index = index;
+ }
+
+ /**
+ * Constructs a copy of the given item.
+ *
+ * @param index
+ * index of the item to be constructed.
+ * @param i
+ * the item that must be copied into the item to be constructed.
+ */
+ Item(final int index, final Item i) {
+ this.index = index;
+ type = i.type;
+ intVal = i.intVal;
+ longVal = i.longVal;
+ strVal1 = i.strVal1;
+ strVal2 = i.strVal2;
+ strVal3 = i.strVal3;
+ hashCode = i.hashCode;
+ }
+
+ /**
+ * Sets this item to an integer item.
+ *
+ * @param intVal
+ * the value of this item.
+ */
+ void set(final int intVal) {
+ this.type = ClassWriter.INT;
+ this.intVal = intVal;
+ this.hashCode = 0x7FFFFFFF & (type + intVal);
+ }
+
+ /**
+ * Sets this item to a long item.
+ *
+ * @param longVal
+ * the value of this item.
+ */
+ void set(final long longVal) {
+ this.type = ClassWriter.LONG;
+ this.longVal = longVal;
+ this.hashCode = 0x7FFFFFFF & (type + (int) longVal);
+ }
+
+ /**
+ * Sets this item to a float item.
+ *
+ * @param floatVal
+ * the value of this item.
+ */
+ void set(final float floatVal) {
+ this.type = ClassWriter.FLOAT;
+ this.intVal = Float.floatToRawIntBits(floatVal);
+ this.hashCode = 0x7FFFFFFF & (type + (int) floatVal);
+ }
+
+ /**
+ * Sets this item to a double item.
+ *
+ * @param doubleVal
+ * the value of this item.
+ */
+ void set(final double doubleVal) {
+ this.type = ClassWriter.DOUBLE;
+ this.longVal = Double.doubleToRawLongBits(doubleVal);
+ this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal);
+ }
+
+ /**
+ * Sets this item to an item that do not hold a primitive value.
+ *
+ * @param type
+ * the type of this item.
+ * @param strVal1
+ * first part of the value of this item.
+ * @param strVal2
+ * second part of the value of this item.
+ * @param strVal3
+ * third part of the value of this item.
+ */
+ @SuppressWarnings("fallthrough")
+ void set(final int type, final String strVal1, final String strVal2,
+ final String strVal3) {
+ this.type = type;
+ this.strVal1 = strVal1;
+ this.strVal2 = strVal2;
+ this.strVal3 = strVal3;
+ switch (type) {
+ case ClassWriter.CLASS:
+ this.intVal = 0; // intVal of a class must be zero, see visitInnerClass
+ case ClassWriter.UTF8:
+ case ClassWriter.STR:
+ case ClassWriter.MTYPE:
+ case ClassWriter.TYPE_NORMAL:
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
+ return;
+ case ClassWriter.NAME_TYPE: {
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
+ * strVal2.hashCode());
+ return;
+ }
+ // ClassWriter.FIELD:
+ // ClassWriter.METH:
+ // ClassWriter.IMETH:
+ // ClassWriter.HANDLE_BASE + 1..9
+ default:
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
+ * strVal2.hashCode() * strVal3.hashCode());
+ }
+ }
+
+ /**
+ * Sets the item to an InvokeDynamic item.
+ *
+ * @param name
+ * invokedynamic's name.
+ * @param desc
+ * invokedynamic's desc.
+ * @param bsmIndex
+ * zero based index into the class attribute BootrapMethods.
+ */
+ void set(String name, String desc, int bsmIndex) {
+ this.type = ClassWriter.INDY;
+ this.longVal = bsmIndex;
+ this.strVal1 = name;
+ this.strVal2 = desc;
+ this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex
+ * strVal1.hashCode() * strVal2.hashCode());
+ }
+
+ /**
+ * Sets the item to a BootstrapMethod item.
+ *
+ * @param position
+ * position in byte in the class attribute BootrapMethods.
+ * @param hashCode
+ * hashcode of the item. This hashcode is processed from the
+ * hashcode of the bootstrap method and the hashcode of all
+ * bootstrap arguments.
+ */
+ void set(int position, int hashCode) {
+ this.type = ClassWriter.BSM;
+ this.intVal = position;
+ this.hashCode = hashCode;
+ }
+
+ /**
+ * Indicates if the given item is equal to this one. This method assumes
+ * that the two items have the same {@link #type} .
+ *
+ * @param i
+ * the item to be compared to this one. Both items must have the
+ * same {@link #type}.
+ * @return true if the given item if equal to this one,
+ * false otherwise.
+ */
+ boolean isEqualTo(final Item i) {
+ switch (type) {
+ case ClassWriter.UTF8:
+ case ClassWriter.STR:
+ case ClassWriter.CLASS:
+ case ClassWriter.MTYPE:
+ case ClassWriter.TYPE_NORMAL:
+ return i.strVal1.equals(strVal1);
+ case ClassWriter.TYPE_MERGED:
+ case ClassWriter.LONG:
+ case ClassWriter.DOUBLE:
+ return i.longVal == longVal;
+ case ClassWriter.INT:
+ case ClassWriter.FLOAT:
+ return i.intVal == intVal;
+ case ClassWriter.TYPE_UNINIT:
+ return i.intVal == intVal && i.strVal1.equals(strVal1);
+ case ClassWriter.NAME_TYPE:
+ return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2);
+ case ClassWriter.INDY: {
+ return i.longVal == longVal && i.strVal1.equals(strVal1)
+ && i.strVal2.equals(strVal2);
+ }
+ // case ClassWriter.FIELD:
+ // case ClassWriter.METH:
+ // case ClassWriter.IMETH:
+ // case ClassWriter.HANDLE_BASE + 1..9
+ default:
+ return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2)
+ && i.strVal3.equals(strVal3);
+ }
+ }
+
+}
diff --git a/src/main/java/org/objectweb/asm/Label.java b/src/main/java/org/objectweb/asm/Label.java
new file mode 100644
index 00000000..6bca6fbe
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Label.java
@@ -0,0 +1,565 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A label represents a position in the bytecode of a method. Labels are used
+ * for jump, goto, and switch instructions, and for try catch blocks. A label
+ * designates the instruction that is just after. Note however that there
+ * can be other elements between a label and the instruction it designates (such
+ * as other labels, stack map frames, line numbers, etc.).
+ *
+ * @author Eric Bruneton
+ */
+public class Label {
+
+ /**
+ * Indicates if this label is only used for debug attributes. Such a label
+ * is not the start of a basic block, the target of a jump instruction, or
+ * an exception handler. It can be safely ignored in control flow graph
+ * analysis algorithms (for optimization purposes).
+ */
+ static final int DEBUG = 1;
+
+ /**
+ * Indicates if the position of this label is known.
+ */
+ static final int RESOLVED = 2;
+
+ /**
+ * Indicates if this label has been updated, after instruction resizing.
+ */
+ static final int RESIZED = 4;
+
+ /**
+ * Indicates if this basic block has been pushed in the basic block stack.
+ * See {@link MethodWriter#visitMaxs visitMaxs}.
+ */
+ static final int PUSHED = 8;
+
+ /**
+ * Indicates if this label is the target of a jump instruction, or the start
+ * of an exception handler.
+ */
+ static final int TARGET = 16;
+
+ /**
+ * Indicates if a stack map frame must be stored for this label.
+ */
+ static final int STORE = 32;
+
+ /**
+ * Indicates if this label corresponds to a reachable basic block.
+ */
+ static final int REACHABLE = 64;
+
+ /**
+ * Indicates if this basic block ends with a JSR instruction.
+ */
+ static final int JSR = 128;
+
+ /**
+ * Indicates if this basic block ends with a RET instruction.
+ */
+ static final int RET = 256;
+
+ /**
+ * Indicates if this basic block is the start of a subroutine.
+ */
+ static final int SUBROUTINE = 512;
+
+ /**
+ * Indicates if this subroutine basic block has been visited by a
+ * visitSubroutine(null, ...) call.
+ */
+ static final int VISITED = 1024;
+
+ /**
+ * Indicates if this subroutine basic block has been visited by a
+ * visitSubroutine(!null, ...) call.
+ */
+ static final int VISITED2 = 2048;
+
+ /**
+ * Field used to associate user information to a label. Warning: this field
+ * is used by the ASM tree package. In order to use it with the ASM tree
+ * package you must override the
+ * {@link org.objectweb.asm.tree.MethodNode#getLabelNode} method.
+ */
+ public Object info;
+
+ /**
+ * Flags that indicate the status of this label.
+ *
+ * @see #DEBUG
+ * @see #RESOLVED
+ * @see #RESIZED
+ * @see #PUSHED
+ * @see #TARGET
+ * @see #STORE
+ * @see #REACHABLE
+ * @see #JSR
+ * @see #RET
+ */
+ int status;
+
+ /**
+ * The line number corresponding to this label, if known. If there are
+ * several lines, each line is stored in a separate label, all linked via
+ * their next field (these links are created in ClassReader and removed just
+ * before visitLabel is called, so that this does not impact the rest of the
+ * code).
+ */
+ int line;
+
+ /**
+ * The position of this label in the code, if known.
+ */
+ int position;
+
+ /**
+ * Number of forward references to this label, times two.
+ */
+ private int referenceCount;
+
+ /**
+ * Informations about forward references. Each forward reference is
+ * described by two consecutive integers in this array: the first one is the
+ * position of the first byte of the bytecode instruction that contains the
+ * forward reference, while the second is the position of the first byte of
+ * the forward reference itself. In fact the sign of the first integer
+ * indicates if this reference uses 2 or 4 bytes, and its absolute value
+ * gives the position of the bytecode instruction. This array is also used
+ * as a bitset to store the subroutines to which a basic block belongs. This
+ * information is needed in {@linked MethodWriter#visitMaxs}, after all
+ * forward references have been resolved. Hence the same array can be used
+ * for both purposes without problems.
+ */
+ private int[] srcAndRefPositions;
+
+ // ------------------------------------------------------------------------
+
+ /*
+ * Fields for the control flow and data flow graph analysis algorithms (used
+ * to compute the maximum stack size or the stack map frames). A control
+ * flow graph contains one node per "basic block", and one edge per "jump"
+ * from one basic block to another. Each node (i.e., each basic block) is
+ * represented by the Label object that corresponds to the first instruction
+ * of this basic block. Each node also stores the list of its successors in
+ * the graph, as a linked list of Edge objects.
+ *
+ * The control flow analysis algorithms used to compute the maximum stack
+ * size or the stack map frames are similar and use two steps. The first
+ * step, during the visit of each instruction, builds information about the
+ * state of the local variables and the operand stack at the end of each
+ * basic block, called the "output frame", relatively to the frame
+ * state at the beginning of the basic block, which is called the "input
+ * frame", and which is unknown during this step. The second step, in
+ * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes
+ * information about the input frame of each basic block, from the input
+ * state of the first basic block (known from the method signature), and by
+ * the using the previously computed relative output frames.
+ *
+ * The algorithm used to compute the maximum stack size only computes the
+ * relative output and absolute input stack heights, while the algorithm
+ * used to compute stack map frames computes relative output frames and
+ * absolute input frames.
+ */
+
+ /**
+ * Start of the output stack relatively to the input stack. The exact
+ * semantics of this field depends on the algorithm that is used.
+ *
+ * When only the maximum stack size is computed, this field is the number of
+ * elements in the input stack.
+ *
+ * When the stack map frames are completely computed, this field is the
+ * offset of the first output stack element relatively to the top of the
+ * input stack. This offset is always negative or null. A null offset means
+ * that the output stack must be appended to the input stack. A -n offset
+ * means that the first n output stack elements must replace the top n input
+ * stack elements, and that the other elements must be appended to the input
+ * stack.
+ */
+ int inputStackTop;
+
+ /**
+ * Maximum height reached by the output stack, relatively to the top of the
+ * input stack. This maximum is always positive or null.
+ */
+ int outputStackMax;
+
+ /**
+ * Information about the input and output stack map frames of this basic
+ * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES}
+ * option is used.
+ */
+ Frame frame;
+
+ /**
+ * The successor of this label, in the order they are visited. This linked
+ * list does not include labels used for debug info only. If
+ * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it
+ * does not contain successive labels that denote the same bytecode position
+ * (in this case only the first label appears in this list).
+ */
+ Label successor;
+
+ /**
+ * The successors of this node in the control flow graph. These successors
+ * are stored in a linked list of {@link Edge Edge} objects, linked to each
+ * other by their {@link Edge#next} field.
+ */
+ Edge successors;
+
+ /**
+ * The next basic block in the basic block stack. This stack is used in the
+ * main loop of the fix point algorithm used in the second step of the
+ * control flow analysis algorithms. It is also used in
+ * {@link #visitSubroutine} to avoid using a recursive method, and in
+ * ClassReader to temporarily store multiple source lines for a label.
+ *
+ * @see MethodWriter#visitMaxs
+ */
+ Label next;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new label.
+ */
+ public Label() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Methods to compute offsets and to manage forward references
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the offset corresponding to this label. This offset is computed
+ * from the start of the method's bytecode. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.
+ *
+ * @return the offset corresponding to this label.
+ * @throws IllegalStateException
+ * if this label is not resolved yet.
+ */
+ public int getOffset() {
+ if ((status & RESOLVED) == 0) {
+ throw new IllegalStateException(
+ "Label offset position has not been resolved yet");
+ }
+ return position;
+ }
+
+ /**
+ * Puts a reference to this label in the bytecode of a method. If the
+ * position of the label is known, the offset is computed and written
+ * directly. Otherwise, a null offset is written and a new forward reference
+ * is declared for this label.
+ *
+ * @param owner
+ * the code writer that calls this method.
+ * @param out
+ * the bytecode of the method.
+ * @param source
+ * the position of first byte of the bytecode instruction that
+ * contains this label.
+ * @param wideOffset
+ * true if the reference must be stored in 4 bytes, or
+ * false if it must be stored with 2 bytes.
+ * @throws IllegalArgumentException
+ * if this label has not been created by the given code writer.
+ */
+ void put(final MethodWriter owner, final ByteVector out, final int source,
+ final boolean wideOffset) {
+ if ((status & RESOLVED) == 0) {
+ if (wideOffset) {
+ addReference(-1 - source, out.length);
+ out.putInt(-1);
+ } else {
+ addReference(source, out.length);
+ out.putShort(-1);
+ }
+ } else {
+ if (wideOffset) {
+ out.putInt(position - source);
+ } else {
+ out.putShort(position - source);
+ }
+ }
+ }
+
+ /**
+ * Adds a forward reference to this label. This method must be called only
+ * for a true forward reference, i.e. only if this label is not resolved
+ * yet. For backward references, the offset of the reference can be, and
+ * must be, computed and stored directly.
+ *
+ * @param sourcePosition
+ * the position of the referencing instruction. This position
+ * will be used to compute the offset of this forward reference.
+ * @param referencePosition
+ * the position where the offset for this forward reference must
+ * be stored.
+ */
+ private void addReference(final int sourcePosition,
+ final int referencePosition) {
+ if (srcAndRefPositions == null) {
+ srcAndRefPositions = new int[6];
+ }
+ if (referenceCount >= srcAndRefPositions.length) {
+ int[] a = new int[srcAndRefPositions.length + 6];
+ System.arraycopy(srcAndRefPositions, 0, a, 0,
+ srcAndRefPositions.length);
+ srcAndRefPositions = a;
+ }
+ srcAndRefPositions[referenceCount++] = sourcePosition;
+ srcAndRefPositions[referenceCount++] = referencePosition;
+ }
+
+ /**
+ * Resolves all forward references to this label. This method must be called
+ * when this label is added to the bytecode of the method, i.e. when its
+ * position becomes known. This method fills in the blanks that where left
+ * in the bytecode by each forward reference previously added to this label.
+ *
+ * @param owner
+ * the code writer that calls this method.
+ * @param position
+ * the position of this label in the bytecode.
+ * @param data
+ * the bytecode of the method.
+ * @return true if a blank that was left for this label was to
+ * small to store the offset. In such a case the corresponding jump
+ * instruction is replaced with a pseudo instruction (using unused
+ * opcodes) using an unsigned two bytes offset. These pseudo
+ * instructions will need to be replaced with true instructions with
+ * wider offsets (4 bytes instead of 2). This is done in
+ * {@link MethodWriter#resizeInstructions}.
+ * @throws IllegalArgumentException
+ * if this label has already been resolved, or if it has not
+ * been created by the given code writer.
+ */
+ boolean resolve(final MethodWriter owner, final int position,
+ final byte[] data) {
+ boolean needUpdate = false;
+ this.status |= RESOLVED;
+ this.position = position;
+ int i = 0;
+ while (i < referenceCount) {
+ int source = srcAndRefPositions[i++];
+ int reference = srcAndRefPositions[i++];
+ int offset;
+ if (source >= 0) {
+ offset = position - source;
+ if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
+ /*
+ * changes the opcode of the jump instruction, in order to
+ * be able to find it later (see resizeInstructions in
+ * MethodWriter). These temporary opcodes are similar to
+ * jump instruction opcodes, except that the 2 bytes offset
+ * is unsigned (and can therefore represent values from 0 to
+ * 65535, which is sufficient since the size of a method is
+ * limited to 65535 bytes).
+ */
+ int opcode = data[reference - 1] & 0xFF;
+ if (opcode <= Opcodes.JSR) {
+ // changes IFEQ ... JSR to opcodes 202 to 217
+ data[reference - 1] = (byte) (opcode + 49);
+ } else {
+ // changes IFNULL and IFNONNULL to opcodes 218 and 219
+ data[reference - 1] = (byte) (opcode + 20);
+ }
+ needUpdate = true;
+ }
+ data[reference++] = (byte) (offset >>> 8);
+ data[reference] = (byte) offset;
+ } else {
+ offset = position + source + 1;
+ data[reference++] = (byte) (offset >>> 24);
+ data[reference++] = (byte) (offset >>> 16);
+ data[reference++] = (byte) (offset >>> 8);
+ data[reference] = (byte) offset;
+ }
+ }
+ return needUpdate;
+ }
+
+ /**
+ * Returns the first label of the series to which this label belongs. For an
+ * isolated label or for the first label in a series of successive labels,
+ * this method returns the label itself. For other labels it returns the
+ * first label of the series.
+ *
+ * @return the first label of the series to which this label belongs.
+ */
+ Label getFirst() {
+ return !ClassReader.FRAMES || frame == null ? this : frame.owner;
+ }
+
+ // ------------------------------------------------------------------------
+ // Methods related to subroutines
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns true is this basic block belongs to the given subroutine.
+ *
+ * @param id
+ * a subroutine id.
+ * @return true is this basic block belongs to the given subroutine.
+ */
+ boolean inSubroutine(final long id) {
+ if ((status & Label.VISITED) != 0) {
+ return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this basic block and the given one belong to a common
+ * subroutine.
+ *
+ * @param block
+ * another basic block.
+ * @return true if this basic block and the given one belong to a common
+ * subroutine.
+ */
+ boolean inSameSubroutine(final Label block) {
+ if ((status & VISITED) == 0 || (block.status & VISITED) == 0) {
+ return false;
+ }
+ for (int i = 0; i < srcAndRefPositions.length; ++i) {
+ if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Marks this basic block as belonging to the given subroutine.
+ *
+ * @param id
+ * a subroutine id.
+ * @param nbSubroutines
+ * the total number of subroutines in the method.
+ */
+ void addToSubroutine(final long id, final int nbSubroutines) {
+ if ((status & VISITED) == 0) {
+ status |= VISITED;
+ srcAndRefPositions = new int[nbSubroutines / 32 + 1];
+ }
+ srcAndRefPositions[(int) (id >>> 32)] |= (int) id;
+ }
+
+ /**
+ * Finds the basic blocks that belong to a given subroutine, and marks these
+ * blocks as belonging to this subroutine. This method follows the control
+ * flow graph to find all the blocks that are reachable from the current
+ * block WITHOUT following any JSR target.
+ *
+ * @param JSR
+ * a JSR block that jumps to this subroutine. If this JSR is not
+ * null it is added to the successor of the RET blocks found in
+ * the subroutine.
+ * @param id
+ * the id of this subroutine.
+ * @param nbSubroutines
+ * the total number of subroutines in the method.
+ */
+ void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) {
+ // user managed stack of labels, to avoid using a recursive method
+ // (recursivity can lead to stack overflow with very large methods)
+ Label stack = this;
+ while (stack != null) {
+ // removes a label l from the stack
+ Label l = stack;
+ stack = l.next;
+ l.next = null;
+
+ if (JSR != null) {
+ if ((l.status & VISITED2) != 0) {
+ continue;
+ }
+ l.status |= VISITED2;
+ // adds JSR to the successors of l, if it is a RET block
+ if ((l.status & RET) != 0) {
+ if (!l.inSameSubroutine(JSR)) {
+ Edge e = new Edge();
+ e.info = l.inputStackTop;
+ e.successor = JSR.successors.successor;
+ e.next = l.successors;
+ l.successors = e;
+ }
+ }
+ } else {
+ // if the l block already belongs to subroutine 'id', continue
+ if (l.inSubroutine(id)) {
+ continue;
+ }
+ // marks the l block as belonging to subroutine 'id'
+ l.addToSubroutine(id, nbSubroutines);
+ }
+ // pushes each successor of l on the stack, except JSR targets
+ Edge e = l.successors;
+ while (e != null) {
+ // if the l block is a JSR block, then 'l.successors.next' leads
+ // to the JSR target (see {@link #visitJumpInsn}) and must
+ // therefore not be followed
+ if ((l.status & Label.JSR) == 0 || e != l.successors.next) {
+ // pushes e.successor on the stack if it not already added
+ if (e.successor.next == null) {
+ e.successor.next = stack;
+ stack = e.successor;
+ }
+ }
+ e = e.next;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Overriden Object methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns a string representation of this label.
+ *
+ * @return a string representation of this label.
+ */
+ @Override
+ public String toString() {
+ return "L" + System.identityHashCode(this);
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/MethodVisitor.java b/src/main/java/org/objectweb/asm/MethodVisitor.java
new file mode 100644
index 00000000..6642524b
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/MethodVisitor.java
@@ -0,0 +1,882 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+
+/**
+ * A visitor to visit a Java method. The methods of this class must be called in
+ * the following order: ( visitParameter )* [
+ * visitAnnotationDefault ] ( visitAnnotation |
+ * visitParameterAnnotation visitTypeAnnotation |
+ * visitAttribute )* [ visitCode ( visitFrame |
+ * visitX Insn | visitLabel |
+ * visitInsnAnnotation | visitTryCatchBlock |
+ * visitTryCatchAnnotation | visitLocalVariable |
+ * visitLocalVariableAnnotation | visitLineNumber )*
+ * visitMaxs ] visitEnd . In addition, the
+ * visitX Insn and visitLabel methods must be called in
+ * the sequential order of the bytecode instructions of the visited code,
+ * visitInsnAnnotation must be called after the annotated
+ * instruction, visitTryCatchBlock must be called before the
+ * labels passed as arguments have been visited,
+ * visitTryCatchBlockAnnotation must be called after the
+ * corresponding try catch block has been visited, and the
+ * visitLocalVariable , visitLocalVariableAnnotation and
+ * visitLineNumber methods must be called after the labels
+ * passed as arguments have been visited.
+ *
+ * @author Eric Bruneton
+ */
+public abstract class MethodVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The method visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected MethodVisitor mv;
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public MethodVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param mv
+ * the method visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public MethodVisitor(final int api, final MethodVisitor mv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.mv = mv;
+ }
+
+ // -------------------------------------------------------------------------
+ // Parameters, annotations and non standard attributes
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a parameter of this method.
+ *
+ * @param name
+ * parameter name or null if none is provided.
+ * @param access
+ * the parameter's access flags, only ACC_FINAL ,
+ * ACC_SYNTHETIC or/and ACC_MANDATED are
+ * allowed (see {@link Opcodes}).
+ */
+ public void visitParameter(String name, int access) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (mv != null) {
+ mv.visitParameter(name, access);
+ }
+ }
+
+ /**
+ * Visits the default value of this annotation interface method.
+ *
+ * @return a visitor to the visit the actual default value of this
+ * annotation interface method, or null if this visitor is
+ * not interested in visiting this default value. The 'name'
+ * parameters passed to the methods of this annotation visitor are
+ * ignored. Moreover, exacly one visit method must be called on this
+ * annotation visitor, followed by visitEnd.
+ */
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mv != null) {
+ return mv.visitAnnotationDefault();
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of this method.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (mv != null) {
+ return mv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the method signature.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#METHOD_TYPE_PARAMETER
+ * METHOD_TYPE_PARAMETER},
+ * {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND
+ * METHOD_TYPE_PARAMETER_BOUND},
+ * {@link TypeReference#METHOD_RETURN METHOD_RETURN},
+ * {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link TypeReference#METHOD_FORMAL_PARAMETER
+ * METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS
+ * THROWS}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (mv != null) {
+ return mv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of a parameter this method.
+ *
+ * @param parameter
+ * the parameter index.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitParameterAnnotation(int parameter,
+ String desc, boolean visible) {
+ if (mv != null) {
+ return mv.visitParameterAnnotation(parameter, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of this method.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (mv != null) {
+ mv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Starts the visit of the method's code, if any (i.e. non abstract method).
+ */
+ public void visitCode() {
+ if (mv != null) {
+ mv.visitCode();
+ }
+ }
+
+ /**
+ * Visits the current state of the local variables and operand stack
+ * elements. This method must(*) be called just before any
+ * instruction i that follows an unconditional branch instruction
+ * such as GOTO or THROW, that is the target of a jump instruction, or that
+ * starts an exception handler block. The visited types must describe the
+ * values of the local variables and of the operand stack elements just
+ * before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or
+ * equal to {@link Opcodes#V1_6 V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in
+ * compressed form (all frames must use the same format, i.e. you must not
+ * mix expanded and compressed frames within a single method):
+ *
+ * In expanded form, all frames must have the F_NEW type.
+ * In compressed form, frames are basically "deltas" from the state of
+ * the previous frame:
+ *
+ * {@link Opcodes#F_SAME} representing frame with exactly the same
+ * locals as the previous frame and with the empty stack.
+ * {@link Opcodes#F_SAME1} representing frame with exactly the same
+ * locals as the previous frame and with single value on the stack (
+ * nStack
is 1 and stack[0]
contains value for the
+ * type of the stack item).
+ * {@link Opcodes#F_APPEND} representing frame with current locals are
+ * the same as the locals in the previous frame, except that additional
+ * locals are defined (nLocal
is 1, 2 or 3 and
+ * local
elements contains values representing added types).
+ * {@link Opcodes#F_CHOP} representing frame with current locals are the
+ * same as the locals in the previous frame, except that the last 1-3 locals
+ * are absent and with the empty stack (nLocals
is 1, 2 or 3).
+ * {@link Opcodes#F_FULL} representing complete frame data.
+ *
+ *
+ *
+ *
+ * In both cases the first frame, corresponding to the method's parameters
+ * and access flags, is implicit and must not be visited. Also, it is
+ * illegal to visit two or more frames for the same code location (i.e., at
+ * least one instruction must be visited between two calls to visitFrame).
+ *
+ * @param type
+ * the type of this stack map frame. Must be
+ * {@link Opcodes#F_NEW} for expanded frames, or
+ * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND},
+ * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or
+ * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for
+ * compressed frames.
+ * @param nLocal
+ * the number of local variables in the visited frame.
+ * @param local
+ * the local variable types in this frame. This array must not be
+ * modified. Primitive types are represented by
+ * {@link Opcodes#TOP}, {@link Opcodes#INTEGER},
+ * {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
+ * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
+ * {@link Opcodes#UNINITIALIZED_THIS} (long and double are
+ * represented by a single element). Reference types are
+ * represented by String objects (representing internal names),
+ * and uninitialized types by Label objects (this label
+ * designates the NEW instruction that created this uninitialized
+ * value).
+ * @param nStack
+ * the number of operand stack elements in the visited frame.
+ * @param stack
+ * the operand stack types in this frame. This array must not be
+ * modified. Its content has the same format as the "local"
+ * array.
+ * @throws IllegalStateException
+ * if a frame is visited just after another one, without any
+ * instruction between the two (unless this frame is a
+ * Opcodes#F_SAME frame, in which case it is silently ignored).
+ */
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack,
+ Object[] stack) {
+ if (mv != null) {
+ mv.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Normal instructions
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a zero operand instruction.
+ *
+ * @param opcode
+ * the opcode of the instruction to be visited. This opcode is
+ * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1,
+ * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1,
+ * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD,
+ * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD,
+ * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
+ * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1,
+ * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB,
+ * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM,
+ * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR,
+ * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D,
+ * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,
+ * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
+ * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER,
+ * or MONITOREXIT.
+ */
+ public void visitInsn(int opcode) {
+ if (mv != null) {
+ mv.visitInsn(opcode);
+ }
+ }
+
+ /**
+ * Visits an instruction with a single int operand.
+ *
+ * @param opcode
+ * the opcode of the instruction to be visited. This opcode is
+ * either BIPUSH, SIPUSH or NEWARRAY.
+ * @param operand
+ * the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between
+ * Byte.MIN_VALUE and Byte.MAX_VALUE.
+ * When opcode is SIPUSH, operand value should be between
+ * Short.MIN_VALUE and Short.MAX_VALUE.
+ * When opcode is NEWARRAY, operand value should be one of
+ * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
+ * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
+ * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
+ * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
+ */
+ public void visitIntInsn(int opcode, int operand) {
+ if (mv != null) {
+ mv.visitIntInsn(opcode, operand);
+ }
+ }
+
+ /**
+ * Visits a local variable instruction. A local variable instruction is an
+ * instruction that loads or stores the value of a local variable.
+ *
+ * @param opcode
+ * the opcode of the local variable instruction to be visited.
+ * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD,
+ * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
+ * @param var
+ * the operand of the instruction to be visited. This operand is
+ * the index of a local variable.
+ */
+ public void visitVarInsn(int opcode, int var) {
+ if (mv != null) {
+ mv.visitVarInsn(opcode, var);
+ }
+ }
+
+ /**
+ * Visits a type instruction. A type instruction is an instruction that
+ * takes the internal name of a class as parameter.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
+ * @param type
+ * the operand of the instruction to be visited. This operand
+ * must be the internal name of an object or array class (see
+ * {@link Type#getInternalName() getInternalName}).
+ */
+ public void visitTypeInsn(int opcode, String type) {
+ if (mv != null) {
+ mv.visitTypeInsn(opcode, type);
+ }
+ }
+
+ /**
+ * Visits a field instruction. A field instruction is an instruction that
+ * loads or stores the value of a field of an object.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
+ * @param owner
+ * the internal name of the field's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type Type}).
+ */
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ if (mv != null) {
+ mv.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that
+ * invokes a method.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
+ * INVOKEINTERFACE.
+ * @param owner
+ * the internal name of the method's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ */
+ @Deprecated
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ if (api >= Opcodes.ASM5) {
+ boolean itf = opcode == Opcodes.INVOKEINTERFACE;
+ visitMethodInsn(opcode, owner, name, desc, itf);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that
+ * invokes a method.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
+ * INVOKEINTERFACE.
+ * @param owner
+ * the internal name of the method's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param itf
+ * if the method's owner class is an interface.
+ */
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc, boolean itf) {
+ if (api < Opcodes.ASM5) {
+ if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
+ throw new IllegalArgumentException(
+ "INVOKESPECIAL/STATIC on interfaces require ASM 5");
+ }
+ visitMethodInsn(opcode, owner, name, desc);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ /**
+ * Visits an invokedynamic instruction.
+ *
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments. Each argument must be
+ * an {@link Integer}, {@link Float}, {@link Long},
+ * {@link Double}, {@link String}, {@link Type} or {@link Handle}
+ * value. This method is allowed to modify the content of the
+ * array so a caller should expect that this array may change.
+ */
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
+ Object... bsmArgs) {
+ if (mv != null) {
+ mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+ }
+
+ /**
+ * Visits a jump instruction. A jump instruction is an instruction that may
+ * jump to another instruction.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
+ * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
+ * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
+ * @param label
+ * the operand of the instruction to be visited. This operand is
+ * a label that designates the instruction to which the jump
+ * instruction may jump.
+ */
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mv != null) {
+ mv.visitJumpInsn(opcode, label);
+ }
+ }
+
+ /**
+ * Visits a label. A label designates the instruction that will be visited
+ * just after it.
+ *
+ * @param label
+ * a {@link Label Label} object.
+ */
+ public void visitLabel(Label label) {
+ if (mv != null) {
+ mv.visitLabel(label);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Special instructions
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a LDC instruction. Note that new constant types may be added in
+ * future versions of the Java Virtual Machine. To easily detect new
+ * constant types, implementations of this method should check for
+ * unexpected constant types, like this:
+ *
+ *
+ * if (cst instanceof Integer) {
+ * // ...
+ * } else if (cst instanceof Float) {
+ * // ...
+ * } else if (cst instanceof Long) {
+ * // ...
+ * } else if (cst instanceof Double) {
+ * // ...
+ * } else if (cst instanceof String) {
+ * // ...
+ * } else if (cst instanceof Type) {
+ * int sort = ((Type) cst).getSort();
+ * if (sort == Type.OBJECT) {
+ * // ...
+ * } else if (sort == Type.ARRAY) {
+ * // ...
+ * } else if (sort == Type.METHOD) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ * } else if (cst instanceof Handle) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ *
+ *
+ * @param cst
+ * the constant to be loaded on the stack. This parameter must be
+ * a non null {@link Integer}, a {@link Float}, a {@link Long}, a
+ * {@link Double}, a {@link String}, a {@link Type} of OBJECT or
+ * ARRAY sort for .class constants, for classes whose
+ * version is 49.0, a {@link Type} of METHOD sort or a
+ * {@link Handle} for MethodType and MethodHandle constants, for
+ * classes whose version is 51.0.
+ */
+ public void visitLdcInsn(Object cst) {
+ if (mv != null) {
+ mv.visitLdcInsn(cst);
+ }
+ }
+
+ /**
+ * Visits an IINC instruction.
+ *
+ * @param var
+ * index of the local variable to be incremented.
+ * @param increment
+ * amount to increment the local variable by.
+ */
+ public void visitIincInsn(int var, int increment) {
+ if (mv != null) {
+ mv.visitIincInsn(var, increment);
+ }
+ }
+
+ /**
+ * Visits a TABLESWITCH instruction.
+ *
+ * @param min
+ * the minimum key value.
+ * @param max
+ * the maximum key value.
+ * @param dflt
+ * beginning of the default handler block.
+ * @param labels
+ * beginnings of the handler blocks. labels[i] is the
+ * beginning of the handler block for the min + i key.
+ */
+ public void visitTableSwitchInsn(int min, int max, Label dflt,
+ Label... labels) {
+ if (mv != null) {
+ mv.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ /**
+ * Visits a LOOKUPSWITCH instruction.
+ *
+ * @param dflt
+ * beginning of the default handler block.
+ * @param keys
+ * the values of the keys.
+ * @param labels
+ * beginnings of the handler blocks. labels[i] is the
+ * beginning of the handler block for the keys[i] key.
+ */
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mv != null) {
+ mv.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ /**
+ * Visits a MULTIANEWARRAY instruction.
+ *
+ * @param desc
+ * an array type descriptor (see {@link Type Type}).
+ * @param dims
+ * number of dimensions of the array to allocate.
+ */
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mv != null) {
+ mv.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ /**
+ * Visits an annotation on an instruction. This method must be called just
+ * after the annotated instruction. It can be called several times
+ * for the same instruction.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#INSTANCEOF INSTANCEOF},
+ * {@link TypeReference#NEW NEW},
+ * {@link TypeReference#CONSTRUCTOR_REFERENCE
+ * CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE
+ * METHOD_REFERENCE}, {@link TypeReference#CAST CAST},
+ * {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitInsnAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (mv != null) {
+ return mv.visitInsnAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------------
+ // Exceptions table entries, debug information, max stack and max locals
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a try catch block.
+ *
+ * @param start
+ * beginning of the exception handler's scope (inclusive).
+ * @param end
+ * end of the exception handler's scope (exclusive).
+ * @param handler
+ * beginning of the exception handler's code.
+ * @param type
+ * internal name of the type of exceptions handled by the
+ * handler, or null to catch any exceptions (for
+ * "finally" blocks).
+ * @throws IllegalArgumentException
+ * if one of the labels has already been visited by this visitor
+ * (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitTryCatchBlock(Label start, Label end, Label handler,
+ String type) {
+ if (mv != null) {
+ mv.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ /**
+ * Visits an annotation on an exception handler type. This method must be
+ * called after the {@link #visitTryCatchBlock} for the annotated
+ * exception handler. It can be called several times for the same exception
+ * handler.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#EXCEPTION_PARAMETER
+ * EXCEPTION_PARAMETER}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (mv != null) {
+ return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a local variable declaration.
+ *
+ * @param name
+ * the name of a local variable.
+ * @param desc
+ * the type descriptor of this local variable.
+ * @param signature
+ * the type signature of this local variable. May be
+ * null if the local variable type does not use generic
+ * types.
+ * @param start
+ * the first instruction corresponding to the scope of this local
+ * variable (inclusive).
+ * @param end
+ * the last instruction corresponding to the scope of this local
+ * variable (exclusive).
+ * @param index
+ * the local variable's index.
+ * @throws IllegalArgumentException
+ * if one of the labels has not already been visited by this
+ * visitor (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mv != null) {
+ mv.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ /**
+ * Visits an annotation on a local variable type.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#LOCAL_VARIABLE
+ * LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE
+ * RESOURCE_VARIABLE}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * null if the annotation targets 'typeRef' as a whole.
+ * @param start
+ * the fist instructions corresponding to the continuous ranges
+ * that make the scope of this local variable (inclusive).
+ * @param end
+ * the last instructions corresponding to the continuous ranges
+ * that make the scope of this local variable (exclusive). This
+ * array must have the same size as the 'start' array.
+ * @param index
+ * the local variable's index in each range. This array must have
+ * the same size as the 'start' array.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * true if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or null if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
+ TypePath typePath, Label[] start, Label[] end, int[] index,
+ String desc, boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ if (mv != null) {
+ return mv.visitLocalVariableAnnotation(typeRef, typePath, start,
+ end, index, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a line number declaration.
+ *
+ * @param line
+ * a line number. This number refers to the source file from
+ * which the class was compiled.
+ * @param start
+ * the first instruction corresponding to this line number.
+ * @throws IllegalArgumentException
+ * if start has not already been visited by this
+ * visitor (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mv != null) {
+ mv.visitLineNumber(line, start);
+ }
+ }
+
+ /**
+ * Visits the maximum stack size and the maximum number of local variables
+ * of the method.
+ *
+ * @param maxStack
+ * maximum stack size of the method.
+ * @param maxLocals
+ * maximum number of local variables for the method.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (mv != null) {
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /**
+ * Visits the end of the method. This method, which is the last one to be
+ * called, is used to inform the visitor that all the annotations and
+ * attributes of the method have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/MethodWriter.java b/src/main/java/org/objectweb/asm/MethodWriter.java
new file mode 100644
index 00000000..f3294167
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/MethodWriter.java
@@ -0,0 +1,2916 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * A {@link MethodVisitor} that generates methods in bytecode form. Each visit
+ * method of this class appends the bytecode corresponding to the visited
+ * instruction to a byte vector, in the order these methods are called.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+class MethodWriter extends MethodVisitor {
+
+ /**
+ * Pseudo access flag used to denote constructors.
+ */
+ static final int ACC_CONSTRUCTOR = 0x80000;
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is zero.
+ */
+ static final int SAME_FRAME = 0; // to 63 (0-3f)
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is 1
+ */
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
+
+ /**
+ * Reserved for future use
+ */
+ static final int RESERVED = 128;
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is 1. Offset is bigger then 63;
+ */
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
+
+ /**
+ * Frame where current locals are the same as the locals in the previous
+ * frame, except that the k last locals are absent. The value of k is given
+ * by the formula 251-frame_type.
+ */
+ static final int CHOP_FRAME = 248; // to 250 (f8-fA)
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is zero. Offset is bigger then 63;
+ */
+ static final int SAME_FRAME_EXTENDED = 251; // fb
+
+ /**
+ * Frame where current locals are the same as the locals in the previous
+ * frame, except that k additional locals are defined. The value of k is
+ * given by the formula frame_type-251.
+ */
+ static final int APPEND_FRAME = 252; // to 254 // fc-fe
+
+ /**
+ * Full frame
+ */
+ static final int FULL_FRAME = 255; // ff
+
+ /**
+ * Indicates that the stack map frames must be recomputed from scratch. In
+ * this case the maximum stack size and number of local variables is also
+ * recomputed from scratch.
+ *
+ * @see #compute
+ */
+ private static final int FRAMES = 0;
+
+ /**
+ * Indicates that the maximum stack size and number of local variables must
+ * be automatically computed.
+ *
+ * @see #compute
+ */
+ private static final int MAXS = 1;
+
+ /**
+ * Indicates that nothing must be automatically computed.
+ *
+ * @see #compute
+ */
+ private static final int NOTHING = 2;
+
+ /**
+ * The class writer to which this method must be added.
+ */
+ final ClassWriter cw;
+
+ /**
+ * Access flags of this method.
+ */
+ private int access;
+
+ /**
+ * The index of the constant pool item that contains the name of this
+ * method.
+ */
+ private final int name;
+
+ /**
+ * The index of the constant pool item that contains the descriptor of this
+ * method.
+ */
+ private final int desc;
+
+ /**
+ * The descriptor of this method.
+ */
+ private final String descriptor;
+
+ /**
+ * The signature of this method.
+ */
+ String signature;
+
+ /**
+ * If not zero, indicates that the code of this method must be copied from
+ * the ClassReader associated to this writer in cw.cr
. More
+ * precisely, this field gives the index of the first byte to copied from
+ * cw.cr.b
.
+ */
+ int classReaderOffset;
+
+ /**
+ * If not zero, indicates that the code of this method must be copied from
+ * the ClassReader associated to this writer in cw.cr
. More
+ * precisely, this field gives the number of bytes to copied from
+ * cw.cr.b
.
+ */
+ int classReaderLength;
+
+ /**
+ * Number of exceptions that can be thrown by this method.
+ */
+ int exceptionCount;
+
+ /**
+ * The exceptions that can be thrown by this method. More precisely, this
+ * array contains the indexes of the constant pool items that contain the
+ * internal names of these exception classes.
+ */
+ int[] exceptions;
+
+ /**
+ * The annotation default attribute of this method. May be null .
+ */
+ private ByteVector annd;
+
+ /**
+ * The runtime visible annotations of this method. May be null .
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this method. May be null .
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this method. May be null
+ * .
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this method. May be
+ * null .
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The runtime visible parameter annotations of this method. May be
+ * null .
+ */
+ private AnnotationWriter[] panns;
+
+ /**
+ * The runtime invisible parameter annotations of this method. May be
+ * null .
+ */
+ private AnnotationWriter[] ipanns;
+
+ /**
+ * The number of synthetic parameters of this method.
+ */
+ private int synthetics;
+
+ /**
+ * The non standard attributes of the method.
+ */
+ private Attribute attrs;
+
+ /**
+ * The bytecode of this method.
+ */
+ private ByteVector code = new ByteVector();
+
+ /**
+ * Maximum stack size of this method.
+ */
+ private int maxStack;
+
+ /**
+ * Maximum number of local variables for this method.
+ */
+ private int maxLocals;
+
+ /**
+ * Number of local variables in the current stack map frame.
+ */
+ private int currentLocals;
+
+ /**
+ * Number of stack map frames in the StackMapTable attribute.
+ */
+ private int frameCount;
+
+ /**
+ * The StackMapTable attribute.
+ */
+ private ByteVector stackMap;
+
+ /**
+ * The offset of the last frame that was written in the StackMapTable
+ * attribute.
+ */
+ private int previousFrameOffset;
+
+ /**
+ * The last frame that was written in the StackMapTable attribute.
+ *
+ * @see #frame
+ */
+ private int[] previousFrame;
+
+ /**
+ * The current stack map frame. The first element contains the offset of the
+ * instruction to which the frame corresponds, the second element is the
+ * number of locals and the third one is the number of stack elements. The
+ * local variables start at index 3 and are followed by the operand stack
+ * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] =
+ * nStack, frame[3] = nLocal. All types are encoded as integers, with the
+ * same format as the one used in {@link Label}, but limited to BASE types.
+ */
+ private int[] frame;
+
+ /**
+ * Number of elements in the exception handler list.
+ */
+ private int handlerCount;
+
+ /**
+ * The first element in the exception handler list.
+ */
+ private Handler firstHandler;
+
+ /**
+ * The last element in the exception handler list.
+ */
+ private Handler lastHandler;
+
+ /**
+ * Number of entries in the MethodParameters attribute.
+ */
+ private int methodParametersCount;
+
+ /**
+ * The MethodParameters attribute.
+ */
+ private ByteVector methodParameters;
+
+ /**
+ * Number of entries in the LocalVariableTable attribute.
+ */
+ private int localVarCount;
+
+ /**
+ * The LocalVariableTable attribute.
+ */
+ private ByteVector localVar;
+
+ /**
+ * Number of entries in the LocalVariableTypeTable attribute.
+ */
+ private int localVarTypeCount;
+
+ /**
+ * The LocalVariableTypeTable attribute.
+ */
+ private ByteVector localVarType;
+
+ /**
+ * Number of entries in the LineNumberTable attribute.
+ */
+ private int lineNumberCount;
+
+ /**
+ * The LineNumberTable attribute.
+ */
+ private ByteVector lineNumber;
+
+ /**
+ * The start offset of the last visited instruction.
+ */
+ private int lastCodeOffset;
+
+ /**
+ * The runtime visible type annotations of the code. May be null .
+ */
+ private AnnotationWriter ctanns;
+
+ /**
+ * The runtime invisible type annotations of the code. May be null .
+ */
+ private AnnotationWriter ictanns;
+
+ /**
+ * The non standard attributes of the method's code.
+ */
+ private Attribute cattrs;
+
+ /**
+ * Indicates if some jump instructions are too small and need to be resized.
+ */
+ private boolean resize;
+
+ /**
+ * The number of subroutines in this method.
+ */
+ private int subroutines;
+
+ // ------------------------------------------------------------------------
+
+ /*
+ * Fields for the control flow graph analysis algorithm (used to compute the
+ * maximum stack size). A control flow graph contains one node per "basic
+ * block", and one edge per "jump" from one basic block to another. Each
+ * node (i.e., each basic block) is represented by the Label object that
+ * corresponds to the first instruction of this basic block. Each node also
+ * stores the list of its successors in the graph, as a linked list of Edge
+ * objects.
+ */
+
+ /**
+ * Indicates what must be automatically computed.
+ *
+ * @see #FRAMES
+ * @see #MAXS
+ * @see #NOTHING
+ */
+ private final int compute;
+
+ /**
+ * A list of labels. This list is the list of basic blocks in the method,
+ * i.e. a list of Label objects linked to each other by their
+ * {@link Label#successor} field, in the order they are visited by
+ * {@link MethodVisitor#visitLabel}, and starting with the first basic
+ * block.
+ */
+ private Label labels;
+
+ /**
+ * The previous basic block.
+ */
+ private Label previousBlock;
+
+ /**
+ * The current basic block.
+ */
+ private Label currentBlock;
+
+ /**
+ * The (relative) stack size after the last visited instruction. This size
+ * is relative to the beginning of the current basic block, i.e., the true
+ * stack size after the last visited instruction is equal to the
+ * {@link Label#inputStackTop beginStackSize} of the current basic block
+ * plus stackSize .
+ */
+ private int stackSize;
+
+ /**
+ * The (relative) maximum stack size after the last visited instruction.
+ * This size is relative to the beginning of the current basic block, i.e.,
+ * the true maximum stack size after the last visited instruction is equal
+ * to the {@link Label#inputStackTop beginStackSize} of the current basic
+ * block plus stackSize .
+ */
+ private int maxStackSize;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link MethodWriter}.
+ *
+ * @param cw
+ * the class writer in which the method must be added.
+ * @param access
+ * the method's access flags (see {@link Opcodes}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type}).
+ * @param signature
+ * the method's signature. May be null .
+ * @param exceptions
+ * the internal names of the method's exceptions. May be
+ * null .
+ * @param computeMaxs
+ * true if the maximum stack size and number of local
+ * variables must be automatically computed.
+ * @param computeFrames
+ * true if the stack map tables must be recomputed from
+ * scratch.
+ */
+ MethodWriter(final ClassWriter cw, final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions, final boolean computeMaxs,
+ final boolean computeFrames) {
+ super(Opcodes.ASM5);
+ if (cw.firstMethod == null) {
+ cw.firstMethod = this;
+ } else {
+ cw.lastMethod.mv = this;
+ }
+ cw.lastMethod = this;
+ this.cw = cw;
+ this.access = access;
+ if ("".equals(name)) {
+ this.access |= ACC_CONSTRUCTOR;
+ }
+ this.name = cw.newUTF8(name);
+ this.desc = cw.newUTF8(desc);
+ this.descriptor = desc;
+ if (ClassReader.SIGNATURES) {
+ this.signature = signature;
+ }
+ if (exceptions != null && exceptions.length > 0) {
+ exceptionCount = exceptions.length;
+ this.exceptions = new int[exceptionCount];
+ for (int i = 0; i < exceptionCount; ++i) {
+ this.exceptions[i] = cw.newClass(exceptions[i]);
+ }
+ }
+ this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
+ if (computeMaxs || computeFrames) {
+ // updates maxLocals
+ int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ if ((access & Opcodes.ACC_STATIC) != 0) {
+ --size;
+ }
+ maxLocals = size;
+ currentLocals = size;
+ // creates and visits the label for the first basic block
+ labels = new Label();
+ labels.status |= Label.PUSHED;
+ visitLabel(labels);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the MethodVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void visitParameter(String name, int access) {
+ if (methodParameters == null) {
+ methodParameters = new ByteVector();
+ }
+ ++methodParametersCount;
+ methodParameters.putShort((name == null) ? 0 : cw.newUTF8(name))
+ .putShort(access);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ annd = new ByteVector();
+ return new AnnotationWriter(cw, false, annd, null, 0);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(final int typeRef,
+ final TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(final int parameter,
+ final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ if ("Ljava/lang/Synthetic;".equals(desc)) {
+ // workaround for a bug in javac with synthetic parameters
+ // see ClassReader.readParameterAnnotations
+ synthetics = Math.max(synthetics, parameter + 1);
+ return new AnnotationWriter(cw, false, bv, null, 0);
+ }
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ if (panns == null) {
+ panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ aw.next = panns[parameter];
+ panns[parameter] = aw;
+ } else {
+ if (ipanns == null) {
+ ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ aw.next = ipanns[parameter];
+ ipanns[parameter] = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attr) {
+ if (attr.isCodeAttribute()) {
+ attr.next = cattrs;
+ cattrs = attr;
+ } else {
+ attr.next = attrs;
+ attrs = attr;
+ }
+ }
+
+ @Override
+ public void visitCode() {
+ }
+
+ @Override
+ public void visitFrame(final int type, final int nLocal,
+ final Object[] local, final int nStack, final Object[] stack) {
+ if (!ClassReader.FRAMES || compute == FRAMES) {
+ return;
+ }
+
+ if (type == Opcodes.F_NEW) {
+ if (previousFrame == null) {
+ visitImplicitFirstFrame();
+ }
+ currentLocals = nLocal;
+ int frameIndex = startFrame(code.length, nLocal, nStack);
+ for (int i = 0; i < nLocal; ++i) {
+ if (local[i] instanceof String) {
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType((String) local[i]);
+ } else if (local[i] instanceof Integer) {
+ frame[frameIndex++] = ((Integer) local[i]).intValue();
+ } else {
+ frame[frameIndex++] = Frame.UNINITIALIZED
+ | cw.addUninitializedType("",
+ ((Label) local[i]).position);
+ }
+ }
+ for (int i = 0; i < nStack; ++i) {
+ if (stack[i] instanceof String) {
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType((String) stack[i]);
+ } else if (stack[i] instanceof Integer) {
+ frame[frameIndex++] = ((Integer) stack[i]).intValue();
+ } else {
+ frame[frameIndex++] = Frame.UNINITIALIZED
+ | cw.addUninitializedType("",
+ ((Label) stack[i]).position);
+ }
+ }
+ endFrame();
+ } else {
+ int delta;
+ if (stackMap == null) {
+ stackMap = new ByteVector();
+ delta = code.length;
+ } else {
+ delta = code.length - previousFrameOffset - 1;
+ if (delta < 0) {
+ if (type == Opcodes.F_SAME) {
+ return;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ switch (type) {
+ case Opcodes.F_FULL:
+ currentLocals = nLocal;
+ stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal);
+ for (int i = 0; i < nLocal; ++i) {
+ writeFrameType(local[i]);
+ }
+ stackMap.putShort(nStack);
+ for (int i = 0; i < nStack; ++i) {
+ writeFrameType(stack[i]);
+ }
+ break;
+ case Opcodes.F_APPEND:
+ currentLocals += nLocal;
+ stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta);
+ for (int i = 0; i < nLocal; ++i) {
+ writeFrameType(local[i]);
+ }
+ break;
+ case Opcodes.F_CHOP:
+ currentLocals -= nLocal;
+ stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta);
+ break;
+ case Opcodes.F_SAME:
+ if (delta < 64) {
+ stackMap.putByte(delta);
+ } else {
+ stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
+ }
+ break;
+ case Opcodes.F_SAME1:
+ if (delta < 64) {
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
+ } else {
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(delta);
+ }
+ writeFrameType(stack[0]);
+ break;
+ }
+
+ previousFrameOffset = code.length;
+ ++frameCount;
+ }
+
+ maxStack = Math.max(maxStack, nStack);
+ maxLocals = Math.max(maxLocals, currentLocals);
+ }
+
+ @Override
+ public void visitInsn(final int opcode) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ code.putByte(opcode);
+ // update currentBlock
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, null, null);
+ } else {
+ // updates current and max stack sizes
+ int size = stackSize + Frame.SIZE[opcode];
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ // if opcode == ATHROW or xRETURN, ends current block (no successor)
+ if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
+ || opcode == Opcodes.ATHROW) {
+ noSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitIntInsn(final int opcode, final int operand) {
+ lastCodeOffset = code.length;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, operand, null, null);
+ } else if (opcode != Opcodes.NEWARRAY) {
+ // updates current and max stack sizes only for NEWARRAY
+ // (stack size variation = 0 for BIPUSH or SIPUSH)
+ int size = stackSize + 1;
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (opcode == Opcodes.SIPUSH) {
+ code.put12(opcode, operand);
+ } else { // BIPUSH or NEWARRAY
+ code.put11(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(final int opcode, final int var) {
+ lastCodeOffset = code.length;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, var, null, null);
+ } else {
+ // updates current and max stack sizes
+ if (opcode == Opcodes.RET) {
+ // no stack change, but end of current block (no successor)
+ currentBlock.status |= Label.RET;
+ // save 'stackSize' here for future use
+ // (see {@link #findSubroutineSuccessors})
+ currentBlock.inputStackTop = stackSize;
+ noSuccessor();
+ } else { // xLOAD or xSTORE
+ int size = stackSize + Frame.SIZE[opcode];
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ }
+ if (compute != NOTHING) {
+ // updates max locals
+ int n;
+ if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD
+ || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
+ n = var + 2;
+ } else {
+ n = var + 1;
+ }
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (var < 4 && opcode != Opcodes.RET) {
+ int opt;
+ if (opcode < Opcodes.ISTORE) {
+ /* ILOAD_0 */
+ opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
+ } else {
+ /* ISTORE_0 */
+ opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
+ }
+ code.putByte(opt);
+ } else if (var >= 256) {
+ code.putByte(196 /* WIDE */).put12(opcode, var);
+ } else {
+ code.put11(opcode, var);
+ }
+ if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) {
+ visitLabel(new Label());
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(final int opcode, final String type) {
+ lastCodeOffset = code.length;
+ Item i = cw.newClassItem(type);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, code.length, cw, i);
+ } else if (opcode == Opcodes.NEW) {
+ // updates current and max stack sizes only if opcode == NEW
+ // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
+ int size = stackSize + 1;
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(opcode, i.index);
+ }
+
+ @Override
+ public void visitFieldInsn(final int opcode, final String owner,
+ final String name, final String desc) {
+ lastCodeOffset = code.length;
+ Item i = cw.newFieldItem(owner, name, desc);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, cw, i);
+ } else {
+ int size;
+ // computes the stack size variation
+ char c = desc.charAt(0);
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
+ break;
+ case Opcodes.PUTSTATIC:
+ size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
+ break;
+ case Opcodes.GETFIELD:
+ size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
+ break;
+ // case Constants.PUTFIELD:
+ default:
+ size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
+ break;
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(opcode, i.index);
+ }
+
+ @Override
+ public void visitMethodInsn(final int opcode, final String owner,
+ final String name, final String desc, final boolean itf) {
+ lastCodeOffset = code.length;
+ Item i = cw.newMethodItem(owner, name, desc, itf);
+ int argSize = i.intVal;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, cw, i);
+ } else {
+ /*
+ * computes the stack size variation. In order not to recompute
+ * several times this variation for the same Item, we use the
+ * intVal field of this item to store this variation, once it
+ * has been computed. More precisely this intVal field stores
+ * the sizes of the arguments and of the return value
+ * corresponding to desc.
+ */
+ if (argSize == 0) {
+ // the above sizes have not been computed yet,
+ // so we compute them...
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ // ... and we save them in order
+ // not to recompute them in the future
+ i.intVal = argSize;
+ }
+ int size;
+ if (opcode == Opcodes.INVOKESTATIC) {
+ size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
+ } else {
+ size = stackSize - (argSize >> 2) + (argSize & 0x03);
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ if (argSize == 0) {
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ i.intVal = argSize;
+ }
+ code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
+ } else {
+ code.put12(opcode, i.index);
+ }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ lastCodeOffset = code.length;
+ Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs);
+ int argSize = i.intVal;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i);
+ } else {
+ /*
+ * computes the stack size variation. In order not to recompute
+ * several times this variation for the same Item, we use the
+ * intVal field of this item to store this variation, once it
+ * has been computed. More precisely this intVal field stores
+ * the sizes of the arguments and of the return value
+ * corresponding to desc.
+ */
+ if (argSize == 0) {
+ // the above sizes have not been computed yet,
+ // so we compute them...
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ // ... and we save them in order
+ // not to recompute them in the future
+ i.intVal = argSize;
+ }
+ int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
+
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(Opcodes.INVOKEDYNAMIC, i.index);
+ code.putShort(0);
+ }
+
+ @Override
+ public void visitJumpInsn(final int opcode, final Label label) {
+ lastCodeOffset = code.length;
+ Label nextInsn = null;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, null, null);
+ // 'label' is the target of a jump instruction
+ label.getFirst().status |= Label.TARGET;
+ // adds 'label' as a successor of this basic block
+ addSuccessor(Edge.NORMAL, label);
+ if (opcode != Opcodes.GOTO) {
+ // creates a Label for the next basic block
+ nextInsn = new Label();
+ }
+ } else {
+ if (opcode == Opcodes.JSR) {
+ if ((label.status & Label.SUBROUTINE) == 0) {
+ label.status |= Label.SUBROUTINE;
+ ++subroutines;
+ }
+ currentBlock.status |= Label.JSR;
+ addSuccessor(stackSize + 1, label);
+ // creates a Label for the next basic block
+ nextInsn = new Label();
+ /*
+ * note that, by construction in this method, a JSR block
+ * has at least two successors in the control flow graph:
+ * the first one leads the next instruction after the JSR,
+ * while the second one leads to the JSR target.
+ */
+ } else {
+ // updates current stack size (max stack size unchanged
+ // because stack size variation always negative in this
+ // case)
+ stackSize += Frame.SIZE[opcode];
+ addSuccessor(stackSize, label);
+ }
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if ((label.status & Label.RESOLVED) != 0
+ && label.position - code.length < Short.MIN_VALUE) {
+ /*
+ * case of a backward jump with an offset < -32768. In this case we
+ * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx
+ * with IFNOTxxx GOTO_W , where IFNOTxxx is the
+ * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where
+ * designates the instruction just after the GOTO_W.
+ */
+ if (opcode == Opcodes.GOTO) {
+ code.putByte(200); // GOTO_W
+ } else if (opcode == Opcodes.JSR) {
+ code.putByte(201); // JSR_W
+ } else {
+ // if the IF instruction is transformed into IFNOT GOTO_W the
+ // next instruction becomes the target of the IFNOT instruction
+ if (nextInsn != null) {
+ nextInsn.status |= Label.TARGET;
+ }
+ code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
+ : opcode ^ 1);
+ code.putShort(8); // jump offset
+ code.putByte(200); // GOTO_W
+ }
+ label.put(this, code, code.length - 1, true);
+ } else {
+ /*
+ * case of a backward jump with an offset >= -32768, or of a forward
+ * jump with, of course, an unknown offset. In these cases we store
+ * the offset in 2 bytes (which will be increased in
+ * resizeInstructions, if needed).
+ */
+ code.putByte(opcode);
+ label.put(this, code, code.length - 1, false);
+ }
+ if (currentBlock != null) {
+ if (nextInsn != null) {
+ // if the jump instruction is not a GOTO, the next instruction
+ // is also a successor of this instruction. Calling visitLabel
+ // adds the label of this next instruction as a successor of the
+ // current block, and starts a new basic block
+ visitLabel(nextInsn);
+ }
+ if (opcode == Opcodes.GOTO) {
+ noSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitLabel(final Label label) {
+ // resolves previous forward references to label, if any
+ resize |= label.resolve(this, code.length, code.data);
+ // updates currentBlock
+ if ((label.status & Label.DEBUG) != 0) {
+ return;
+ }
+ if (compute == FRAMES) {
+ if (currentBlock != null) {
+ if (label.position == currentBlock.position) {
+ // successive labels, do not start a new basic block
+ currentBlock.status |= (label.status & Label.TARGET);
+ label.frame = currentBlock.frame;
+ return;
+ }
+ // ends current block (with one new successor)
+ addSuccessor(Edge.NORMAL, label);
+ }
+ // begins a new current block
+ currentBlock = label;
+ if (label.frame == null) {
+ label.frame = new Frame();
+ label.frame.owner = label;
+ }
+ // updates the basic block list
+ if (previousBlock != null) {
+ if (label.position == previousBlock.position) {
+ previousBlock.status |= (label.status & Label.TARGET);
+ label.frame = previousBlock.frame;
+ currentBlock = previousBlock;
+ return;
+ }
+ previousBlock.successor = label;
+ }
+ previousBlock = label;
+ } else if (compute == MAXS) {
+ if (currentBlock != null) {
+ // ends current block (with one new successor)
+ currentBlock.outputStackMax = maxStackSize;
+ addSuccessor(stackSize, label);
+ }
+ // begins a new current block
+ currentBlock = label;
+ // resets the relative current and max stack sizes
+ stackSize = 0;
+ maxStackSize = 0;
+ // updates the basic block list
+ if (previousBlock != null) {
+ previousBlock.successor = label;
+ }
+ previousBlock = label;
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(final Object cst) {
+ lastCodeOffset = code.length;
+ Item i = cw.newConstItem(cst);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
+ } else {
+ int size;
+ // computes the stack size variation
+ if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
+ size = stackSize + 2;
+ } else {
+ size = stackSize + 1;
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ int index = i.index;
+ if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
+ code.put12(20 /* LDC2_W */, index);
+ } else if (index >= 256) {
+ code.put12(19 /* LDC_W */, index);
+ } else {
+ code.put11(Opcodes.LDC, index);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(final int var, final int increment) {
+ lastCodeOffset = code.length;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.IINC, var, null, null);
+ }
+ }
+ if (compute != NOTHING) {
+ // updates max locals
+ int n = var + 1;
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if ((var > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var)
+ .putShort(increment);
+ } else {
+ code.putByte(Opcodes.IINC).put11(var, increment);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(final int min, final int max,
+ final Label dflt, final Label... labels) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ int source = code.length;
+ code.putByte(Opcodes.TABLESWITCH);
+ code.putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(this, code, source, true);
+ code.putInt(min).putInt(max);
+ for (int i = 0; i < labels.length; ++i) {
+ labels[i].put(this, code, source, true);
+ }
+ // updates currentBlock
+ visitSwitchInsn(dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
+ final Label[] labels) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ int source = code.length;
+ code.putByte(Opcodes.LOOKUPSWITCH);
+ code.putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(this, code, source, true);
+ code.putInt(labels.length);
+ for (int i = 0; i < labels.length; ++i) {
+ code.putInt(keys[i]);
+ labels[i].put(this, code, source, true);
+ }
+ // updates currentBlock
+ visitSwitchInsn(dflt, labels);
+ }
+
+ private void visitSwitchInsn(final Label dflt, final Label[] labels) {
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
+ // adds current block successors
+ addSuccessor(Edge.NORMAL, dflt);
+ dflt.getFirst().status |= Label.TARGET;
+ for (int i = 0; i < labels.length; ++i) {
+ addSuccessor(Edge.NORMAL, labels[i]);
+ labels[i].getFirst().status |= Label.TARGET;
+ }
+ } else {
+ // updates current stack size (max stack size unchanged)
+ --stackSize;
+ // adds current block successors
+ addSuccessor(stackSize, dflt);
+ for (int i = 0; i < labels.length; ++i) {
+ addSuccessor(stackSize, labels[i]);
+ }
+ }
+ // ends current block
+ noSuccessor();
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(final String desc, final int dims) {
+ lastCodeOffset = code.length;
+ Item i = cw.newClassItem(desc);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
+ } else {
+ // updates current stack size (max stack size unchanged because
+ // stack size variation always negative or null)
+ stackSize += 1 - dims;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims);
+ }
+
+ @Override
+ public AnnotationVisitor visitInsnAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ typeRef = (typeRef & 0xFF0000FF) | (lastCodeOffset << 8);
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitTryCatchBlock(final Label start, final Label end,
+ final Label handler, final String type) {
+ ++handlerCount;
+ Handler h = new Handler();
+ h.start = start;
+ h.end = end;
+ h.handler = handler;
+ h.desc = type;
+ h.type = type != null ? cw.newClass(type) : 0;
+ if (lastHandler == null) {
+ firstHandler = h;
+ } else {
+ lastHandler.next = h;
+ }
+ lastHandler = h;
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitLocalVariable(final String name, final String desc,
+ final String signature, final Label start, final Label end,
+ final int index) {
+ if (signature != null) {
+ if (localVarType == null) {
+ localVarType = new ByteVector();
+ }
+ ++localVarTypeCount;
+ localVarType.putShort(start.position)
+ .putShort(end.position - start.position)
+ .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature))
+ .putShort(index);
+ }
+ if (localVar == null) {
+ localVar = new ByteVector();
+ }
+ ++localVarCount;
+ localVar.putShort(start.position)
+ .putShort(end.position - start.position)
+ .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc))
+ .putShort(index);
+ if (compute != NOTHING) {
+ // updates max locals
+ char c = desc.charAt(0);
+ int n = index + (c == 'J' || c == 'D' ? 2 : 1);
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
+ TypePath typePath, Label[] start, Label[] end, int[] index,
+ String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ bv.putByte(typeRef >>> 24).putShort(start.length);
+ for (int i = 0; i < start.length; ++i) {
+ bv.putShort(start[i].position)
+ .putShort(end[i].position - start[i].position)
+ .putShort(index[i]);
+ }
+ if (typePath == null) {
+ bv.putByte(0);
+ } else {
+ int length = typePath.b[typePath.offset] * 2 + 1;
+ bv.putByteArray(typePath.b, typePath.offset, length);
+ }
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ if (lineNumber == null) {
+ lineNumber = new ByteVector();
+ }
+ ++lineNumberCount;
+ lineNumber.putShort(start.position);
+ lineNumber.putShort(line);
+ }
+
+ @Override
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (resize) {
+ // replaces the temporary jump opcodes introduced by Label.resolve.
+ if (ClassReader.RESIZE) {
+ resizeInstructions();
+ } else {
+ throw new RuntimeException("Method code too large!");
+ }
+ }
+ if (ClassReader.FRAMES && compute == FRAMES) {
+ // completes the control flow graph with exception handler blocks
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label l = handler.start.getFirst();
+ Label h = handler.handler.getFirst();
+ Label e = handler.end.getFirst();
+ // computes the kind of the edges to 'h'
+ String t = handler.desc == null ? "java/lang/Throwable"
+ : handler.desc;
+ int kind = Frame.OBJECT | cw.addType(t);
+ // h is an exception handler
+ h.status |= Label.TARGET;
+ // adds 'h' as a successor of labels between 'start' and 'end'
+ while (l != e) {
+ // creates an edge to 'h'
+ Edge b = new Edge();
+ b.info = kind;
+ b.successor = h;
+ // adds it to the successors of 'l'
+ b.next = l.successors;
+ l.successors = b;
+ // goes to the next label
+ l = l.successor;
+ }
+ handler = handler.next;
+ }
+
+
+ // creates and visits the first (implicit) frame
+ Frame f = labels.frame;
+ Type[] args = Type.getArgumentTypes(descriptor);
+ f.initInputFrame(cw, access, args, this.maxLocals);
+ visitFrame(f);
+
+ /*
+ * fix point algorithm: mark the first basic block as 'changed'
+ * (i.e. put it in the 'changed' list) and, while there are changed
+ * basic blocks, choose one, mark it as unchanged, and update its
+ * successors (which can be changed in the process).
+ */
+ int max = 0;
+ Label changed = labels;
+ while (changed != null) {
+ // removes a basic block from the list of changed basic blocks
+ Label l = changed;
+ changed = changed.next;
+ l.next = null;
+ f = l.frame;
+ // a reachable jump target must be stored in the stack map
+ if ((l.status & Label.TARGET) != 0) {
+ l.status |= Label.STORE;
+ }
+ // all visited labels are reachable, by definition
+ l.status |= Label.REACHABLE;
+ // updates the (absolute) maximum stack size
+ int blockMax = f.inputStack.length + l.outputStackMax;
+ if (blockMax > max) {
+ max = blockMax;
+ }
+ // updates the successors of the current basic block
+ Edge e = l.successors;
+ while (e != null) {
+ Label n = e.successor.getFirst();
+ boolean change = f.merge(cw, n.frame, e.info);
+ if (change && n.next == null) {
+ // if n has changed and is not already in the 'changed'
+ // list, adds it to this list
+ n.next = changed;
+ changed = n;
+ }
+ e = e.next;
+ }
+ }
+
+ // visits all the frames that must be stored in the stack map
+ Label l = labels;
+ while (l != null) {
+ f = l.frame;
+ if ((l.status & Label.STORE) != 0) {
+ visitFrame(f);
+ }
+ if ((l.status & Label.REACHABLE) == 0) {
+ // finds start and end of dead basic block
+ Label k = l.successor;
+ int start = l.position;
+ int end = (k == null ? code.length : k.position) - 1;
+ // if non empty basic block
+ if (end >= start) {
+ max = Math.max(max, 1);
+ // replaces instructions with NOP ... NOP ATHROW
+ for (int i = start; i < end; ++i) {
+ code.data[i] = Opcodes.NOP;
+ }
+ code.data[end] = (byte) Opcodes.ATHROW;
+ // emits a frame for this unreachable block
+ int frameIndex = startFrame(start, 0, 1);
+ frame[frameIndex] = Frame.OBJECT
+ | cw.addType("java/lang/Throwable");
+ endFrame();
+ // removes the start-end range from the exception
+ // handlers
+ firstHandler = Handler.remove(firstHandler, l, k);
+ }
+ }
+ l = l.successor;
+ }
+
+ handler = firstHandler;
+ handlerCount = 0;
+ while (handler != null) {
+ handlerCount += 1;
+ handler = handler.next;
+ }
+
+ this.maxStack = max;
+ } else if (compute == MAXS) {
+ // completes the control flow graph with exception handler blocks
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label l = handler.start;
+ Label h = handler.handler;
+ Label e = handler.end;
+ // adds 'h' as a successor of labels between 'start' and 'end'
+ while (l != e) {
+ // creates an edge to 'h'
+ Edge b = new Edge();
+ b.info = Edge.EXCEPTION;
+ b.successor = h;
+ // adds it to the successors of 'l'
+ if ((l.status & Label.JSR) == 0) {
+ b.next = l.successors;
+ l.successors = b;
+ } else {
+ // if l is a JSR block, adds b after the first two edges
+ // to preserve the hypothesis about JSR block successors
+ // order (see {@link #visitJumpInsn})
+ b.next = l.successors.next.next;
+ l.successors.next.next = b;
+ }
+ // goes to the next label
+ l = l.successor;
+ }
+ handler = handler.next;
+ }
+
+ if (subroutines > 0) {
+ // completes the control flow graph with the RET successors
+ /*
+ * first step: finds the subroutines. This step determines, for
+ * each basic block, to which subroutine(s) it belongs.
+ */
+ // finds the basic blocks that belong to the "main" subroutine
+ int id = 0;
+ labels.visitSubroutine(null, 1, subroutines);
+ // finds the basic blocks that belong to the real subroutines
+ Label l = labels;
+ while (l != null) {
+ if ((l.status & Label.JSR) != 0) {
+ // the subroutine is defined by l's TARGET, not by l
+ Label subroutine = l.successors.next.successor;
+ // if this subroutine has not been visited yet...
+ if ((subroutine.status & Label.VISITED) == 0) {
+ // ...assigns it a new id and finds its basic blocks
+ id += 1;
+ subroutine.visitSubroutine(null, (id / 32L) << 32
+ | (1L << (id % 32)), subroutines);
+ }
+ }
+ l = l.successor;
+ }
+ // second step: finds the successors of RET blocks
+ l = labels;
+ while (l != null) {
+ if ((l.status & Label.JSR) != 0) {
+ Label L = labels;
+ while (L != null) {
+ L.status &= ~Label.VISITED2;
+ L = L.successor;
+ }
+ // the subroutine is defined by l's TARGET, not by l
+ Label subroutine = l.successors.next.successor;
+ subroutine.visitSubroutine(l, 0, subroutines);
+ }
+ l = l.successor;
+ }
+ }
+
+ /*
+ * control flow analysis algorithm: while the block stack is not
+ * empty, pop a block from this stack, update the max stack size,
+ * compute the true (non relative) begin stack size of the
+ * successors of this block, and push these successors onto the
+ * stack (unless they have already been pushed onto the stack).
+ * Note: by hypothesis, the {@link Label#inputStackTop} of the
+ * blocks in the block stack are the true (non relative) beginning
+ * stack sizes of these blocks.
+ */
+ int max = 0;
+ Label stack = labels;
+ while (stack != null) {
+ // pops a block from the stack
+ Label l = stack;
+ stack = stack.next;
+ // computes the true (non relative) max stack size of this block
+ int start = l.inputStackTop;
+ int blockMax = start + l.outputStackMax;
+ // updates the global max stack size
+ if (blockMax > max) {
+ max = blockMax;
+ }
+ // analyzes the successors of the block
+ Edge b = l.successors;
+ if ((l.status & Label.JSR) != 0) {
+ // ignores the first edge of JSR blocks (virtual successor)
+ b = b.next;
+ }
+ while (b != null) {
+ l = b.successor;
+ // if this successor has not already been pushed...
+ if ((l.status & Label.PUSHED) == 0) {
+ // computes its true beginning stack size...
+ l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start
+ + b.info;
+ // ...and pushes it onto the stack
+ l.status |= Label.PUSHED;
+ l.next = stack;
+ stack = l;
+ }
+ b = b.next;
+ }
+ }
+ this.maxStack = Math.max(maxStack, max);
+ } else {
+ this.maxStack = maxStack;
+ this.maxLocals = maxLocals;
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: control flow analysis algorithm
+ // ------------------------------------------------------------------------
+
+ /**
+ * Adds a successor to the {@link #currentBlock currentBlock} block.
+ *
+ * @param info
+ * information about the control flow edge to be added.
+ * @param successor
+ * the successor block to be added to the current block.
+ */
+ private void addSuccessor(final int info, final Label successor) {
+ // creates and initializes an Edge object...
+ Edge b = new Edge();
+ b.info = info;
+ b.successor = successor;
+ // ...and adds it to the successor list of the currentBlock block
+ b.next = currentBlock.successors;
+ currentBlock.successors = b;
+ }
+
+ /**
+ * Ends the current basic block. This method must be used in the case where
+ * the current basic block does not have any successor.
+ */
+ private void noSuccessor() {
+ if (compute == FRAMES) {
+ Label l = new Label();
+ l.frame = new Frame();
+ l.frame.owner = l;
+ l.resolve(this, code.length, code.data);
+ previousBlock.successor = l;
+ previousBlock = l;
+ } else {
+ currentBlock.outputStackMax = maxStackSize;
+ }
+ currentBlock = null;
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: stack map frames
+ // ------------------------------------------------------------------------
+
+ /**
+ * Visits a frame that has been computed from scratch.
+ *
+ * @param f
+ * the frame that must be visited.
+ */
+ private void visitFrame(final Frame f) {
+ int i, t;
+ int nTop = 0;
+ int nLocal = 0;
+ int nStack = 0;
+ int[] locals = f.inputLocals;
+ int[] stacks = f.inputStack;
+ // computes the number of locals (ignores TOP types that are just after
+ // a LONG or a DOUBLE, and all trailing TOP types)
+ for (i = 0; i < locals.length; ++i) {
+ t = locals[i];
+ if (t == Frame.TOP) {
+ ++nTop;
+ } else {
+ nLocal += nTop + 1;
+ nTop = 0;
+ }
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ // computes the stack size (ignores TOP types that are just after
+ // a LONG or a DOUBLE)
+ for (i = 0; i < stacks.length; ++i) {
+ t = stacks[i];
+ ++nStack;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ // visits the frame and its content
+ int frameIndex = startFrame(f.owner.position, nLocal, nStack);
+ for (i = 0; nLocal > 0; ++i, --nLocal) {
+ t = locals[i];
+ frame[frameIndex++] = t;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ for (i = 0; i < stacks.length; ++i) {
+ t = stacks[i];
+ frame[frameIndex++] = t;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ endFrame();
+ }
+
+ /**
+ * Visit the implicit first frame of this method.
+ */
+ private void visitImplicitFirstFrame() {
+ // There can be at most descriptor.length() + 1 locals
+ int frameIndex = startFrame(0, descriptor.length() + 1, 0);
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & ACC_CONSTRUCTOR) == 0) {
+ frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName);
+ } else {
+ frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS;
+ }
+ }
+ int i = 1;
+ loop: while (true) {
+ int j = i;
+ switch (descriptor.charAt(i++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ frame[frameIndex++] = 1; // Opcodes.INTEGER;
+ break;
+ case 'F':
+ frame[frameIndex++] = 2; // Opcodes.FLOAT;
+ break;
+ case 'J':
+ frame[frameIndex++] = 4; // Opcodes.LONG;
+ break;
+ case 'D':
+ frame[frameIndex++] = 3; // Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (descriptor.charAt(i) == '[') {
+ ++i;
+ }
+ if (descriptor.charAt(i) == 'L') {
+ ++i;
+ while (descriptor.charAt(i) != ';') {
+ ++i;
+ }
+ }
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType(descriptor.substring(j, ++i));
+ break;
+ case 'L':
+ while (descriptor.charAt(i) != ';') {
+ ++i;
+ }
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType(descriptor.substring(j + 1, i++));
+ break;
+ default:
+ break loop;
+ }
+ }
+ frame[1] = frameIndex - 3;
+ endFrame();
+ }
+
+ /**
+ * Starts the visit of a stack map frame.
+ *
+ * @param offset
+ * the offset of the instruction to which the frame corresponds.
+ * @param nLocal
+ * the number of local variables in the frame.
+ * @param nStack
+ * the number of stack elements in the frame.
+ * @return the index of the next element to be written in this frame.
+ */
+ private int startFrame(final int offset, final int nLocal, final int nStack) {
+ int n = 3 + nLocal + nStack;
+ if (frame == null || frame.length < n) {
+ frame = new int[n];
+ }
+ frame[0] = offset;
+ frame[1] = nLocal;
+ frame[2] = nStack;
+ return 3;
+ }
+
+ /**
+ * Checks if the visit of the current frame {@link #frame} is finished, and
+ * if yes, write it in the StackMapTable attribute.
+ */
+ private void endFrame() {
+ if (previousFrame != null) { // do not write the first frame
+ if (stackMap == null) {
+ stackMap = new ByteVector();
+ }
+ writeFrame();
+ ++frameCount;
+ }
+ previousFrame = frame;
+ frame = null;
+ }
+
+ /**
+ * Compress and writes the current frame {@link #frame} in the StackMapTable
+ * attribute.
+ */
+ private void writeFrame() {
+ int clocalsSize = frame[1];
+ int cstackSize = frame[2];
+ if ((cw.version & 0xFFFF) < Opcodes.V1_6) {
+ stackMap.putShort(frame[0]).putShort(clocalsSize);
+ writeFrameTypes(3, 3 + clocalsSize);
+ stackMap.putShort(cstackSize);
+ writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
+ return;
+ }
+ int localsSize = previousFrame[1];
+ int type = FULL_FRAME;
+ int k = 0;
+ int delta;
+ if (frameCount == 0) {
+ delta = frame[0];
+ } else {
+ delta = frame[0] - previousFrame[0] - 1;
+ }
+ if (cstackSize == 0) {
+ k = clocalsSize - localsSize;
+ switch (k) {
+ case -3:
+ case -2:
+ case -1:
+ type = CHOP_FRAME;
+ localsSize = clocalsSize;
+ break;
+ case 0:
+ type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ type = APPEND_FRAME;
+ break;
+ }
+ } else if (clocalsSize == localsSize && cstackSize == 1) {
+ type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME
+ : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
+ }
+ if (type != FULL_FRAME) {
+ // verify if locals are the same
+ int l = 3;
+ for (int j = 0; j < localsSize; j++) {
+ if (frame[l] != previousFrame[l]) {
+ type = FULL_FRAME;
+ break;
+ }
+ l++;
+ }
+ }
+ switch (type) {
+ case SAME_FRAME:
+ stackMap.putByte(delta);
+ break;
+ case SAME_LOCALS_1_STACK_ITEM_FRAME:
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
+ writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
+ break;
+ case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort(
+ delta);
+ writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
+ break;
+ case SAME_FRAME_EXTENDED:
+ stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
+ break;
+ case CHOP_FRAME:
+ stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
+ break;
+ case APPEND_FRAME:
+ stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
+ writeFrameTypes(3 + localsSize, 3 + clocalsSize);
+ break;
+ // case FULL_FRAME:
+ default:
+ stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize);
+ writeFrameTypes(3, 3 + clocalsSize);
+ stackMap.putShort(cstackSize);
+ writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
+ }
+ }
+
+ /**
+ * Writes some types of the current frame {@link #frame} into the
+ * StackMapTableAttribute. This method converts types from the format used
+ * in {@link Label} to the format used in StackMapTable attributes. In
+ * particular, it converts type table indexes to constant pool indexes.
+ *
+ * @param start
+ * index of the first type in {@link #frame} to write.
+ * @param end
+ * index of last type in {@link #frame} to write (exclusive).
+ */
+ private void writeFrameTypes(final int start, final int end) {
+ for (int i = start; i < end; ++i) {
+ int t = frame[i];
+ int d = t & Frame.DIM;
+ if (d == 0) {
+ int v = t & Frame.BASE_VALUE;
+ switch (t & Frame.BASE_KIND) {
+ case Frame.OBJECT:
+ stackMap.putByte(7).putShort(
+ cw.newClass(cw.typeTable[v].strVal1));
+ break;
+ case Frame.UNINITIALIZED:
+ stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
+ break;
+ default:
+ stackMap.putByte(v);
+ }
+ } else {
+ StringBuilder sb = new StringBuilder();
+ d >>= 28;
+ while (d-- > 0) {
+ sb.append('[');
+ }
+ if ((t & Frame.BASE_KIND) == Frame.OBJECT) {
+ sb.append('L');
+ sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
+ sb.append(';');
+ } else {
+ switch (t & 0xF) {
+ case 1:
+ sb.append('I');
+ break;
+ case 2:
+ sb.append('F');
+ break;
+ case 3:
+ sb.append('D');
+ break;
+ case 9:
+ sb.append('Z');
+ break;
+ case 10:
+ sb.append('B');
+ break;
+ case 11:
+ sb.append('C');
+ break;
+ case 12:
+ sb.append('S');
+ break;
+ default:
+ sb.append('J');
+ }
+ }
+ stackMap.putByte(7).putShort(cw.newClass(sb.toString()));
+ }
+ }
+ }
+
+ private void writeFrameType(final Object type) {
+ if (type instanceof String) {
+ stackMap.putByte(7).putShort(cw.newClass((String) type));
+ } else if (type instanceof Integer) {
+ stackMap.putByte(((Integer) type).intValue());
+ } else {
+ stackMap.putByte(8).putShort(((Label) type).position);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: dump bytecode array
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of the bytecode of this method.
+ *
+ * @return the size of the bytecode of this method.
+ */
+ final int getSize() {
+ if (classReaderOffset != 0) {
+ return 6 + classReaderLength;
+ }
+ int size = 8;
+ if (code.length > 0) {
+ if (code.length > 65536) {
+ throw new RuntimeException("Method code too large!");
+ }
+ cw.newUTF8("Code");
+ size += 18 + code.length + 8 * handlerCount;
+ if (localVar != null) {
+ cw.newUTF8("LocalVariableTable");
+ size += 8 + localVar.length;
+ }
+ if (localVarType != null) {
+ cw.newUTF8("LocalVariableTypeTable");
+ size += 8 + localVarType.length;
+ }
+ if (lineNumber != null) {
+ cw.newUTF8("LineNumberTable");
+ size += 8 + lineNumber.length;
+ }
+ if (stackMap != null) {
+ boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
+ cw.newUTF8(zip ? "StackMapTable" : "StackMap");
+ size += 8 + stackMap.length;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + ctanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + ictanns.getSize();
+ }
+ if (cattrs != null) {
+ size += cattrs.getSize(cw, code.data, code.length, maxStack,
+ maxLocals);
+ }
+ }
+ if (exceptionCount > 0) {
+ cw.newUTF8("Exceptions");
+ size += 8 + 2 * exceptionCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ cw.newUTF8("Synthetic");
+ size += 6;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ cw.newUTF8("Deprecated");
+ size += 6;
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ cw.newUTF8("Signature");
+ cw.newUTF8(signature);
+ size += 8;
+ }
+ if (methodParameters != null) {
+ cw.newUTF8("MethodParameters");
+ size += 7 + methodParameters.length;
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ cw.newUTF8("AnnotationDefault");
+ size += 6 + annd.length;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ cw.newUTF8("RuntimeVisibleAnnotations");
+ size += 8 + anns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ cw.newUTF8("RuntimeInvisibleAnnotations");
+ size += 8 + ianns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + tanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + itanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ cw.newUTF8("RuntimeVisibleParameterAnnotations");
+ size += 7 + 2 * (panns.length - synthetics);
+ for (int i = panns.length - 1; i >= synthetics; --i) {
+ size += panns[i] == null ? 0 : panns[i].getSize();
+ }
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ cw.newUTF8("RuntimeInvisibleParameterAnnotations");
+ size += 7 + 2 * (ipanns.length - synthetics);
+ for (int i = ipanns.length - 1; i >= synthetics; --i) {
+ size += ipanns[i] == null ? 0 : ipanns[i].getSize();
+ }
+ }
+ if (attrs != null) {
+ size += attrs.getSize(cw, null, 0, -1, -1);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the bytecode of this method in the given byte vector.
+ *
+ * @param out
+ * the byte vector into which the bytecode of this method must be
+ * copied.
+ */
+ final void put(final ByteVector out) {
+ final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
+ int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
+ out.putShort(access & ~mask).putShort(name).putShort(desc);
+ if (classReaderOffset != 0) {
+ out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
+ return;
+ }
+ int attributeCount = 0;
+ if (code.length > 0) {
+ ++attributeCount;
+ }
+ if (exceptionCount > 0) {
+ ++attributeCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ ++attributeCount;
+ }
+ if (methodParameters != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ ++attributeCount;
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (code.length > 0) {
+ int size = 12 + code.length + 8 * handlerCount;
+ if (localVar != null) {
+ size += 8 + localVar.length;
+ }
+ if (localVarType != null) {
+ size += 8 + localVarType.length;
+ }
+ if (lineNumber != null) {
+ size += 8 + lineNumber.length;
+ }
+ if (stackMap != null) {
+ size += 8 + stackMap.length;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ size += 8 + ctanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ size += 8 + ictanns.getSize();
+ }
+ if (cattrs != null) {
+ size += cattrs.getSize(cw, code.data, code.length, maxStack,
+ maxLocals);
+ }
+ out.putShort(cw.newUTF8("Code")).putInt(size);
+ out.putShort(maxStack).putShort(maxLocals);
+ out.putInt(code.length).putByteArray(code.data, 0, code.length);
+ out.putShort(handlerCount);
+ if (handlerCount > 0) {
+ Handler h = firstHandler;
+ while (h != null) {
+ out.putShort(h.start.position).putShort(h.end.position)
+ .putShort(h.handler.position).putShort(h.type);
+ h = h.next;
+ }
+ }
+ attributeCount = 0;
+ if (localVar != null) {
+ ++attributeCount;
+ }
+ if (localVarType != null) {
+ ++attributeCount;
+ }
+ if (lineNumber != null) {
+ ++attributeCount;
+ }
+ if (stackMap != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ ++attributeCount;
+ }
+ if (cattrs != null) {
+ attributeCount += cattrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (localVar != null) {
+ out.putShort(cw.newUTF8("LocalVariableTable"));
+ out.putInt(localVar.length + 2).putShort(localVarCount);
+ out.putByteArray(localVar.data, 0, localVar.length);
+ }
+ if (localVarType != null) {
+ out.putShort(cw.newUTF8("LocalVariableTypeTable"));
+ out.putInt(localVarType.length + 2).putShort(localVarTypeCount);
+ out.putByteArray(localVarType.data, 0, localVarType.length);
+ }
+ if (lineNumber != null) {
+ out.putShort(cw.newUTF8("LineNumberTable"));
+ out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
+ out.putByteArray(lineNumber.data, 0, lineNumber.length);
+ }
+ if (stackMap != null) {
+ boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
+ out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
+ out.putInt(stackMap.length + 2).putShort(frameCount);
+ out.putByteArray(stackMap.data, 0, stackMap.length);
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ ctanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ ictanns.put(out);
+ }
+ if (cattrs != null) {
+ cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out);
+ }
+ }
+ if (exceptionCount > 0) {
+ out.putShort(cw.newUTF8("Exceptions")).putInt(
+ 2 * exceptionCount + 2);
+ out.putShort(exceptionCount);
+ for (int i = 0; i < exceptionCount; ++i) {
+ out.putShort(exceptions[i]);
+ }
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(cw.newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(cw.newUTF8("Deprecated")).putInt(0);
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ out.putShort(cw.newUTF8("Signature")).putInt(2)
+ .putShort(cw.newUTF8(signature));
+ }
+ if (methodParameters != null) {
+ out.putShort(cw.newUTF8("MethodParameters"));
+ out.putInt(methodParameters.length + 1).putByte(
+ methodParametersCount);
+ out.putByteArray(methodParameters.data, 0, methodParameters.length);
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ out.putShort(cw.newUTF8("AnnotationDefault"));
+ out.putInt(annd.length);
+ out.putByteArray(annd.data, 0, annd.length);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
+ AnnotationWriter.put(panns, synthetics, out);
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
+ AnnotationWriter.put(ipanns, synthetics, out);
+ }
+ if (attrs != null) {
+ attrs.put(cw, null, 0, -1, -1, out);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W)
+ // ------------------------------------------------------------------------
+
+ /**
+ * Resizes and replaces the temporary instructions inserted by
+ * {@link Label#resolve} for wide forward jumps, while keeping jump offsets
+ * and instruction addresses consistent. This may require to resize other
+ * existing instructions, or even to introduce new instructions: for
+ * example, increasing the size of an instruction by 2 at the middle of a
+ * method can increases the offset of an IFEQ instruction from 32766 to
+ * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W
+ * 32765. This, in turn, may require to increase the size of another jump
+ * instruction, and so on... All these operations are handled automatically
+ * by this method.
+ *
+ * This method must be called after all the method that is being built
+ * has been visited . In particular, the {@link Label Label} objects used
+ * to construct the method are no longer valid after this method has been
+ * called.
+ */
+ private void resizeInstructions() {
+ byte[] b = code.data; // bytecode of the method
+ int u, v, label; // indexes in b
+ int i, j; // loop indexes
+ /*
+ * 1st step: As explained above, resizing an instruction may require to
+ * resize another one, which may require to resize yet another one, and
+ * so on. The first step of the algorithm consists in finding all the
+ * instructions that need to be resized, without modifying the code.
+ * This is done by the following "fix point" algorithm:
+ *
+ * Parse the code to find the jump instructions whose offset will need
+ * more than 2 bytes to be stored (the future offset is computed from
+ * the current offset and from the number of bytes that will be inserted
+ * or removed between the source and target instructions). For each such
+ * instruction, adds an entry in (a copy of) the indexes and sizes
+ * arrays (if this has not already been done in a previous iteration!).
+ *
+ * If at least one entry has been added during the previous step, go
+ * back to the beginning, otherwise stop.
+ *
+ * In fact the real algorithm is complicated by the fact that the size
+ * of TABLESWITCH and LOOKUPSWITCH instructions depends on their
+ * position in the bytecode (because of padding). In order to ensure the
+ * convergence of the algorithm, the number of bytes to be added or
+ * removed from these instructions is over estimated during the previous
+ * loop, and computed exactly only after the loop is finished (this
+ * requires another pass to parse the bytecode of the method).
+ */
+ int[] allIndexes = new int[0]; // copy of indexes
+ int[] allSizes = new int[0]; // copy of sizes
+ boolean[] resize; // instructions to be resized
+ int newOffset; // future offset of a jump instruction
+
+ resize = new boolean[code.length];
+
+ // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done
+ int state = 3;
+ do {
+ if (state == 3) {
+ state = 2;
+ }
+ u = 0;
+ while (u < b.length) {
+ int opcode = b[u] & 0xFF; // opcode of current instruction
+ int insert = 0; // bytes to be added after this instruction
+
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ if (opcode > 201) {
+ // converts temporary opcodes 202 to 217, 218 and
+ // 219 to IFEQ ... JSR (inclusive), IFNULL and
+ // IFNONNULL
+ opcode = opcode < 218 ? opcode - 49 : opcode - 20;
+ label = u + readUnsignedShort(b, u + 1);
+ } else {
+ label = u + readShort(b, u + 1);
+ }
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ if (newOffset < Short.MIN_VALUE
+ || newOffset > Short.MAX_VALUE) {
+ if (!resize[u]) {
+ if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
+ // two additional bytes will be required to
+ // replace this GOTO or JSR instruction with
+ // a GOTO_W or a JSR_W
+ insert = 2;
+ } else {
+ // five additional bytes will be required to
+ // replace this IFxxx instruction with
+ // IFNOTxxx GOTO_W , where IFNOTxxx
+ // is the "opposite" opcode of IFxxx (i.e.,
+ // IFNE for IFEQ) and where designates
+ // the instruction just after the GOTO_W.
+ insert = 5;
+ }
+ resize[u] = true;
+ }
+ }
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ u += 5;
+ break;
+ case ClassWriter.TABL_INSN:
+ if (state == 1) {
+ // true number of bytes to be added (or removed)
+ // from this instruction = (future number of padding
+ // bytes - current number of padding byte) -
+ // previously over estimated variation =
+ // = ((3 - newOffset%4) - (3 - u%4)) - u%4
+ // = (-newOffset%4 + u%4) - u%4
+ // = -(newOffset & 3)
+ newOffset = getNewOffset(allIndexes, allSizes, 0, u);
+ insert = -(newOffset & 3);
+ } else if (!resize[u]) {
+ // over estimation of the number of bytes to be
+ // added to this instruction = 3 - current number
+ // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
+ insert = u & 3;
+ resize[u] = true;
+ }
+ // skips instruction
+ u = u + 4 - (u & 3);
+ u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12;
+ break;
+ case ClassWriter.LOOK_INSN:
+ if (state == 1) {
+ // like TABL_INSN
+ newOffset = getNewOffset(allIndexes, allSizes, 0, u);
+ insert = -(newOffset & 3);
+ } else if (!resize[u]) {
+ // like TABL_INSN
+ insert = u & 3;
+ resize[u] = true;
+ }
+ // skips instruction
+ u = u + 4 - (u & 3);
+ u += 8 * readInt(b, u + 4) + 8;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ u += 6;
+ } else {
+ u += 4;
+ }
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ u += 5;
+ break;
+ // case ClassWriter.MANA_INSN:
+ default:
+ u += 4;
+ break;
+ }
+ if (insert != 0) {
+ // adds a new (u, insert) entry in the allIndexes and
+ // allSizes arrays
+ int[] newIndexes = new int[allIndexes.length + 1];
+ int[] newSizes = new int[allSizes.length + 1];
+ System.arraycopy(allIndexes, 0, newIndexes, 0,
+ allIndexes.length);
+ System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length);
+ newIndexes[allIndexes.length] = u;
+ newSizes[allSizes.length] = insert;
+ allIndexes = newIndexes;
+ allSizes = newSizes;
+ if (insert > 0) {
+ state = 3;
+ }
+ }
+ }
+ if (state < 3) {
+ --state;
+ }
+ } while (state != 0);
+
+ // 2nd step:
+ // copies the bytecode of the method into a new bytevector, updates the
+ // offsets, and inserts (or removes) bytes as requested.
+
+ ByteVector newCode = new ByteVector(code.length);
+
+ u = 0;
+ while (u < code.length) {
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ newCode.putByte(opcode);
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ if (opcode > 201) {
+ // changes temporary opcodes 202 to 217 (inclusive), 218
+ // and 219 to IFEQ ... JSR (inclusive), IFNULL and
+ // IFNONNULL
+ opcode = opcode < 218 ? opcode - 49 : opcode - 20;
+ label = u + readUnsignedShort(b, u + 1);
+ } else {
+ label = u + readShort(b, u + 1);
+ }
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ if (resize[u]) {
+ // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
+ // with IFNOTxxx GOTO_W , where IFNOTxxx is
+ // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
+ // and where designates the instruction just after
+ // the GOTO_W.
+ if (opcode == Opcodes.GOTO) {
+ newCode.putByte(200); // GOTO_W
+ } else if (opcode == Opcodes.JSR) {
+ newCode.putByte(201); // JSR_W
+ } else {
+ newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
+ : opcode ^ 1);
+ newCode.putShort(8); // jump offset
+ newCode.putByte(200); // GOTO_W
+ // newOffset now computed from start of GOTO_W
+ newOffset -= 3;
+ }
+ newCode.putInt(newOffset);
+ } else {
+ newCode.putByte(opcode);
+ newCode.putShort(newOffset);
+ }
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ label = u + readInt(b, u + 1);
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ newCode.putByte(opcode);
+ newCode.putInt(newOffset);
+ u += 5;
+ break;
+ case ClassWriter.TABL_INSN:
+ // skips 0 to 3 padding bytes
+ v = u;
+ u = u + 4 - (v & 3);
+ // reads and copies instruction
+ newCode.putByte(Opcodes.TABLESWITCH);
+ newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ j = readInt(b, u);
+ u += 4;
+ newCode.putInt(j);
+ j = readInt(b, u) - j + 1;
+ u += 4;
+ newCode.putInt(readInt(b, u - 4));
+ for (; j > 0; --j) {
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ }
+ break;
+ case ClassWriter.LOOK_INSN:
+ // skips 0 to 3 padding bytes
+ v = u;
+ u = u + 4 - (v & 3);
+ // reads and copies instruction
+ newCode.putByte(Opcodes.LOOKUPSWITCH);
+ newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ j = readInt(b, u);
+ u += 4;
+ newCode.putInt(j);
+ for (; j > 0; --j) {
+ newCode.putInt(readInt(b, u));
+ u += 4;
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ }
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ newCode.putByteArray(b, u, 6);
+ u += 6;
+ } else {
+ newCode.putByteArray(b, u, 4);
+ u += 4;
+ }
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ newCode.putByteArray(b, u, 2);
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ newCode.putByteArray(b, u, 3);
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ newCode.putByteArray(b, u, 5);
+ u += 5;
+ break;
+ // case MANA_INSN:
+ default:
+ newCode.putByteArray(b, u, 4);
+ u += 4;
+ break;
+ }
+ }
+
+ // updates the stack map frame labels
+ if (compute == FRAMES) {
+ Label l = labels;
+ while (l != null) {
+ /*
+ * Detects the labels that are just after an IF instruction that
+ * has been resized with the IFNOT GOTO_W pattern. These labels
+ * are now the target of a jump instruction (the IFNOT
+ * instruction). Note that we need the original label position
+ * here. getNewOffset must therefore never have been called for
+ * this label.
+ */
+ u = l.position - 3;
+ if (u >= 0 && resize[u]) {
+ l.status |= Label.TARGET;
+ }
+ getNewOffset(allIndexes, allSizes, l);
+ l = l.successor;
+ }
+ // Update the offsets in the uninitialized types
+ if (cw.typeTable != null) {
+ for (i = 0; i < cw.typeTable.length; ++i) {
+ Item item = cw.typeTable[i];
+ if (item != null && item.type == ClassWriter.TYPE_UNINIT) {
+ item.intVal = getNewOffset(allIndexes, allSizes, 0,
+ item.intVal);
+ }
+ }
+ }
+ // The stack map frames are not serialized yet, so we don't need
+ // to update them. They will be serialized in visitMaxs.
+ } else if (frameCount > 0) {
+ /*
+ * Resizing an existing stack map frame table is really hard. Not
+ * only the table must be parsed to update the offets, but new
+ * frames may be needed for jump instructions that were inserted by
+ * this method. And updating the offsets or inserting frames can
+ * change the format of the following frames, in case of packed
+ * frames. In practice the whole table must be recomputed. For this
+ * the frames are marked as potentially invalid. This will cause the
+ * whole class to be reread and rewritten with the COMPUTE_FRAMES
+ * option (see the ClassWriter.toByteArray method). This is not very
+ * efficient but is much easier and requires much less code than any
+ * other method I can think of.
+ */
+ cw.invalidFrames = true;
+ }
+ // updates the exception handler block labels
+ Handler h = firstHandler;
+ while (h != null) {
+ getNewOffset(allIndexes, allSizes, h.start);
+ getNewOffset(allIndexes, allSizes, h.end);
+ getNewOffset(allIndexes, allSizes, h.handler);
+ h = h.next;
+ }
+ // updates the instructions addresses in the
+ // local var and line number tables
+ for (i = 0; i < 2; ++i) {
+ ByteVector bv = i == 0 ? localVar : localVarType;
+ if (bv != null) {
+ b = bv.data;
+ u = 0;
+ while (u < bv.length) {
+ label = readUnsignedShort(b, u);
+ newOffset = getNewOffset(allIndexes, allSizes, 0, label);
+ writeShort(b, u, newOffset);
+ label += readUnsignedShort(b, u + 2);
+ newOffset = getNewOffset(allIndexes, allSizes, 0, label)
+ - newOffset;
+ writeShort(b, u + 2, newOffset);
+ u += 10;
+ }
+ }
+ }
+ if (lineNumber != null) {
+ b = lineNumber.data;
+ u = 0;
+ while (u < lineNumber.length) {
+ writeShort(
+ b,
+ u,
+ getNewOffset(allIndexes, allSizes, 0,
+ readUnsignedShort(b, u)));
+ u += 4;
+ }
+ }
+ // updates the labels of the other attributes
+ Attribute attr = cattrs;
+ while (attr != null) {
+ Label[] labels = attr.getLabels();
+ if (labels != null) {
+ for (i = labels.length - 1; i >= 0; --i) {
+ getNewOffset(allIndexes, allSizes, labels[i]);
+ }
+ }
+ attr = attr.next;
+ }
+
+ // replaces old bytecodes with new ones
+ code = newCode;
+ }
+
+ /**
+ * Reads an unsigned short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static int readUnsignedShort(final byte[] b, final int index) {
+ return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static short readShort(final byte[] b, final int index) {
+ return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static int readInt(final byte[] b, final int index) {
+ return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
+ | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
+ }
+
+ /**
+ * Writes a short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * where the first byte of the short value must be written.
+ * @param s
+ * the value to be written in the given byte array.
+ */
+ static void writeShort(final byte[] b, final int index, final int s) {
+ b[index] = (byte) (s >>> 8);
+ b[index + 1] = (byte) s;
+ }
+
+ /**
+ * Computes the future value of a bytecode offset.
+ *
+ * Note: it is possible to have several entries for the same instruction in
+ * the indexes and sizes : two entries (index=a,size=b) and
+ * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b').
+ *
+ * @param indexes
+ * current positions of the instructions to be resized. Each
+ * instruction must be designated by the index of its last
+ * byte, plus one (or, in other words, by the index of the
+ * first byte of the next instruction).
+ * @param sizes
+ * the number of bytes to be added to the above
+ * instructions. More precisely, for each i < len ,
+ * sizes [i] bytes will be added at the end of the
+ * instruction designated by indexes [i] or, if
+ * sizes [i] is negative, the last |
+ * sizes[i] | bytes of the instruction will be removed
+ * (the instruction size must not become negative or
+ * null).
+ * @param begin
+ * index of the first byte of the source instruction.
+ * @param end
+ * index of the first byte of the target instruction.
+ * @return the future value of the given bytecode offset.
+ */
+ static int getNewOffset(final int[] indexes, final int[] sizes,
+ final int begin, final int end) {
+ int offset = end - begin;
+ for (int i = 0; i < indexes.length; ++i) {
+ if (begin < indexes[i] && indexes[i] <= end) {
+ // forward jump
+ offset += sizes[i];
+ } else if (end < indexes[i] && indexes[i] <= begin) {
+ // backward jump
+ offset -= sizes[i];
+ }
+ }
+ return offset;
+ }
+
+ /**
+ * Updates the offset of the given label.
+ *
+ * @param indexes
+ * current positions of the instructions to be resized. Each
+ * instruction must be designated by the index of its last
+ * byte, plus one (or, in other words, by the index of the
+ * first byte of the next instruction).
+ * @param sizes
+ * the number of bytes to be added to the above
+ * instructions. More precisely, for each i < len ,
+ * sizes [i] bytes will be added at the end of the
+ * instruction designated by indexes [i] or, if
+ * sizes [i] is negative, the last |
+ * sizes[i] | bytes of the instruction will be removed
+ * (the instruction size must not become negative or
+ * null).
+ * @param label
+ * the label whose offset must be updated.
+ */
+ static void getNewOffset(final int[] indexes, final int[] sizes,
+ final Label label) {
+ if ((label.status & Label.RESIZED) == 0) {
+ label.position = getNewOffset(indexes, sizes, 0, label.position);
+ label.status |= Label.RESIZED;
+ }
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/Opcodes.java b/src/main/java/org/objectweb/asm/Opcodes.java
new file mode 100644
index 00000000..e5c2b33f
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Opcodes.java
@@ -0,0 +1,361 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+/**
+ * Defines the JVM opcodes, access flags and array type codes. This interface
+ * does not define all the JVM opcodes because some opcodes are automatically
+ * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced
+ * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n
+ * opcodes are therefore not defined in this interface. Likewise for LDC,
+ * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and
+ * JSR_W.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public interface Opcodes {
+
+ // ASM API versions
+
+ int ASM4 = 4 << 16 | 0 << 8 | 0;
+ int ASM5 = 5 << 16 | 0 << 8 | 0;
+
+ // versions
+
+ int V1_1 = 3 << 16 | 45;
+ int V1_2 = 0 << 16 | 46;
+ int V1_3 = 0 << 16 | 47;
+ int V1_4 = 0 << 16 | 48;
+ int V1_5 = 0 << 16 | 49;
+ int V1_6 = 0 << 16 | 50;
+ int V1_7 = 0 << 16 | 51;
+ int V1_8 = 0 << 16 | 52;
+
+ // access flags
+
+ int ACC_PUBLIC = 0x0001; // class, field, method
+ int ACC_PRIVATE = 0x0002; // class, field, method
+ int ACC_PROTECTED = 0x0004; // class, field, method
+ int ACC_STATIC = 0x0008; // field, method
+ int ACC_FINAL = 0x0010; // class, field, method, parameter
+ int ACC_SUPER = 0x0020; // class
+ int ACC_SYNCHRONIZED = 0x0020; // method
+ int ACC_VOLATILE = 0x0040; // field
+ int ACC_BRIDGE = 0x0040; // method
+ int ACC_VARARGS = 0x0080; // method
+ int ACC_TRANSIENT = 0x0080; // field
+ int ACC_NATIVE = 0x0100; // method
+ int ACC_INTERFACE = 0x0200; // class
+ int ACC_ABSTRACT = 0x0400; // class, method
+ int ACC_STRICT = 0x0800; // method
+ int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter
+ int ACC_ANNOTATION = 0x2000; // class
+ int ACC_ENUM = 0x4000; // class(?) field inner
+ int ACC_MANDATED = 0x8000; // parameter
+
+ // ASM specific pseudo access flags
+
+ int ACC_DEPRECATED = 0x20000; // class, field, method
+
+ // types for NEWARRAY
+
+ int T_BOOLEAN = 4;
+ int T_CHAR = 5;
+ int T_FLOAT = 6;
+ int T_DOUBLE = 7;
+ int T_BYTE = 8;
+ int T_SHORT = 9;
+ int T_INT = 10;
+ int T_LONG = 11;
+
+ // tags for Handle
+
+ int H_GETFIELD = 1;
+ int H_GETSTATIC = 2;
+ int H_PUTFIELD = 3;
+ int H_PUTSTATIC = 4;
+ int H_INVOKEVIRTUAL = 5;
+ int H_INVOKESTATIC = 6;
+ int H_INVOKESPECIAL = 7;
+ int H_NEWINVOKESPECIAL = 8;
+ int H_INVOKEINTERFACE = 9;
+
+ // stack map frame types
+
+ /**
+ * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}.
+ */
+ int F_NEW = -1;
+
+ /**
+ * Represents a compressed frame with complete frame data.
+ */
+ int F_FULL = 0;
+
+ /**
+ * Represents a compressed frame where locals are the same as the locals in
+ * the previous frame, except that additional 1-3 locals are defined, and
+ * with an empty stack.
+ */
+ int F_APPEND = 1;
+
+ /**
+ * Represents a compressed frame where locals are the same as the locals in
+ * the previous frame, except that the last 1-3 locals are absent and with
+ * an empty stack.
+ */
+ int F_CHOP = 2;
+
+ /**
+ * Represents a compressed frame with exactly the same locals as the
+ * previous frame and with an empty stack.
+ */
+ int F_SAME = 3;
+
+ /**
+ * Represents a compressed frame with exactly the same locals as the
+ * previous frame and with a single value on the stack.
+ */
+ int F_SAME1 = 4;
+
+ Integer TOP = new Integer(0);
+ Integer INTEGER = new Integer(1);
+ Integer FLOAT = new Integer(2);
+ Integer DOUBLE = new Integer(3);
+ Integer LONG = new Integer(4);
+ Integer NULL = new Integer(5);
+ Integer UNINITIALIZED_THIS = new Integer(6);
+
+ // opcodes // visit method (- = idem)
+
+ int NOP = 0; // visitInsn
+ int ACONST_NULL = 1; // -
+ int ICONST_M1 = 2; // -
+ int ICONST_0 = 3; // -
+ int ICONST_1 = 4; // -
+ int ICONST_2 = 5; // -
+ int ICONST_3 = 6; // -
+ int ICONST_4 = 7; // -
+ int ICONST_5 = 8; // -
+ int LCONST_0 = 9; // -
+ int LCONST_1 = 10; // -
+ int FCONST_0 = 11; // -
+ int FCONST_1 = 12; // -
+ int FCONST_2 = 13; // -
+ int DCONST_0 = 14; // -
+ int DCONST_1 = 15; // -
+ int BIPUSH = 16; // visitIntInsn
+ int SIPUSH = 17; // -
+ int LDC = 18; // visitLdcInsn
+ // int LDC_W = 19; // -
+ // int LDC2_W = 20; // -
+ int ILOAD = 21; // visitVarInsn
+ int LLOAD = 22; // -
+ int FLOAD = 23; // -
+ int DLOAD = 24; // -
+ int ALOAD = 25; // -
+ // int ILOAD_0 = 26; // -
+ // int ILOAD_1 = 27; // -
+ // int ILOAD_2 = 28; // -
+ // int ILOAD_3 = 29; // -
+ // int LLOAD_0 = 30; // -
+ // int LLOAD_1 = 31; // -
+ // int LLOAD_2 = 32; // -
+ // int LLOAD_3 = 33; // -
+ // int FLOAD_0 = 34; // -
+ // int FLOAD_1 = 35; // -
+ // int FLOAD_2 = 36; // -
+ // int FLOAD_3 = 37; // -
+ // int DLOAD_0 = 38; // -
+ // int DLOAD_1 = 39; // -
+ // int DLOAD_2 = 40; // -
+ // int DLOAD_3 = 41; // -
+ // int ALOAD_0 = 42; // -
+ // int ALOAD_1 = 43; // -
+ // int ALOAD_2 = 44; // -
+ // int ALOAD_3 = 45; // -
+ int IALOAD = 46; // visitInsn
+ int LALOAD = 47; // -
+ int FALOAD = 48; // -
+ int DALOAD = 49; // -
+ int AALOAD = 50; // -
+ int BALOAD = 51; // -
+ int CALOAD = 52; // -
+ int SALOAD = 53; // -
+ int ISTORE = 54; // visitVarInsn
+ int LSTORE = 55; // -
+ int FSTORE = 56; // -
+ int DSTORE = 57; // -
+ int ASTORE = 58; // -
+ // int ISTORE_0 = 59; // -
+ // int ISTORE_1 = 60; // -
+ // int ISTORE_2 = 61; // -
+ // int ISTORE_3 = 62; // -
+ // int LSTORE_0 = 63; // -
+ // int LSTORE_1 = 64; // -
+ // int LSTORE_2 = 65; // -
+ // int LSTORE_3 = 66; // -
+ // int FSTORE_0 = 67; // -
+ // int FSTORE_1 = 68; // -
+ // int FSTORE_2 = 69; // -
+ // int FSTORE_3 = 70; // -
+ // int DSTORE_0 = 71; // -
+ // int DSTORE_1 = 72; // -
+ // int DSTORE_2 = 73; // -
+ // int DSTORE_3 = 74; // -
+ // int ASTORE_0 = 75; // -
+ // int ASTORE_1 = 76; // -
+ // int ASTORE_2 = 77; // -
+ // int ASTORE_3 = 78; // -
+ int IASTORE = 79; // visitInsn
+ int LASTORE = 80; // -
+ int FASTORE = 81; // -
+ int DASTORE = 82; // -
+ int AASTORE = 83; // -
+ int BASTORE = 84; // -
+ int CASTORE = 85; // -
+ int SASTORE = 86; // -
+ int POP = 87; // -
+ int POP2 = 88; // -
+ int DUP = 89; // -
+ int DUP_X1 = 90; // -
+ int DUP_X2 = 91; // -
+ int DUP2 = 92; // -
+ int DUP2_X1 = 93; // -
+ int DUP2_X2 = 94; // -
+ int SWAP = 95; // -
+ int IADD = 96; // -
+ int LADD = 97; // -
+ int FADD = 98; // -
+ int DADD = 99; // -
+ int ISUB = 100; // -
+ int LSUB = 101; // -
+ int FSUB = 102; // -
+ int DSUB = 103; // -
+ int IMUL = 104; // -
+ int LMUL = 105; // -
+ int FMUL = 106; // -
+ int DMUL = 107; // -
+ int IDIV = 108; // -
+ int LDIV = 109; // -
+ int FDIV = 110; // -
+ int DDIV = 111; // -
+ int IREM = 112; // -
+ int LREM = 113; // -
+ int FREM = 114; // -
+ int DREM = 115; // -
+ int INEG = 116; // -
+ int LNEG = 117; // -
+ int FNEG = 118; // -
+ int DNEG = 119; // -
+ int ISHL = 120; // -
+ int LSHL = 121; // -
+ int ISHR = 122; // -
+ int LSHR = 123; // -
+ int IUSHR = 124; // -
+ int LUSHR = 125; // -
+ int IAND = 126; // -
+ int LAND = 127; // -
+ int IOR = 128; // -
+ int LOR = 129; // -
+ int IXOR = 130; // -
+ int LXOR = 131; // -
+ int IINC = 132; // visitIincInsn
+ int I2L = 133; // visitInsn
+ int I2F = 134; // -
+ int I2D = 135; // -
+ int L2I = 136; // -
+ int L2F = 137; // -
+ int L2D = 138; // -
+ int F2I = 139; // -
+ int F2L = 140; // -
+ int F2D = 141; // -
+ int D2I = 142; // -
+ int D2L = 143; // -
+ int D2F = 144; // -
+ int I2B = 145; // -
+ int I2C = 146; // -
+ int I2S = 147; // -
+ int LCMP = 148; // -
+ int FCMPL = 149; // -
+ int FCMPG = 150; // -
+ int DCMPL = 151; // -
+ int DCMPG = 152; // -
+ int IFEQ = 153; // visitJumpInsn
+ int IFNE = 154; // -
+ int IFLT = 155; // -
+ int IFGE = 156; // -
+ int IFGT = 157; // -
+ int IFLE = 158; // -
+ int IF_ICMPEQ = 159; // -
+ int IF_ICMPNE = 160; // -
+ int IF_ICMPLT = 161; // -
+ int IF_ICMPGE = 162; // -
+ int IF_ICMPGT = 163; // -
+ int IF_ICMPLE = 164; // -
+ int IF_ACMPEQ = 165; // -
+ int IF_ACMPNE = 166; // -
+ int GOTO = 167; // -
+ int JSR = 168; // -
+ int RET = 169; // visitVarInsn
+ int TABLESWITCH = 170; // visiTableSwitchInsn
+ int LOOKUPSWITCH = 171; // visitLookupSwitch
+ int IRETURN = 172; // visitInsn
+ int LRETURN = 173; // -
+ int FRETURN = 174; // -
+ int DRETURN = 175; // -
+ int ARETURN = 176; // -
+ int RETURN = 177; // -
+ int GETSTATIC = 178; // visitFieldInsn
+ int PUTSTATIC = 179; // -
+ int GETFIELD = 180; // -
+ int PUTFIELD = 181; // -
+ int INVOKEVIRTUAL = 182; // visitMethodInsn
+ int INVOKESPECIAL = 183; // -
+ int INVOKESTATIC = 184; // -
+ int INVOKEINTERFACE = 185; // -
+ int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
+ int NEW = 187; // visitTypeInsn
+ int NEWARRAY = 188; // visitIntInsn
+ int ANEWARRAY = 189; // visitTypeInsn
+ int ARRAYLENGTH = 190; // visitInsn
+ int ATHROW = 191; // -
+ int CHECKCAST = 192; // visitTypeInsn
+ int INSTANCEOF = 193; // -
+ int MONITORENTER = 194; // visitInsn
+ int MONITOREXIT = 195; // -
+ // int WIDE = 196; // NOT VISITED
+ int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
+ int IFNULL = 198; // visitJumpInsn
+ int IFNONNULL = 199; // -
+ // int GOTO_W = 200; // -
+ // int JSR_W = 201; // -
+}
diff --git a/src/main/java/org/objectweb/asm/Type.java b/src/main/java/org/objectweb/asm/Type.java
new file mode 100644
index 00000000..5a66950b
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/Type.java
@@ -0,0 +1,908 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * A Java field or method type. This class can be used to make it easier to
+ * manipulate type and method descriptors.
+ *
+ * @author Eric Bruneton
+ * @author Chris Nokleberg
+ */
+public class Type {
+
+ /**
+ * The sort of the void type. See {@link #getSort getSort}.
+ */
+ public static final int VOID = 0;
+
+ /**
+ * The sort of the boolean type. See {@link #getSort getSort}.
+ */
+ public static final int BOOLEAN = 1;
+
+ /**
+ * The sort of the char type. See {@link #getSort getSort}.
+ */
+ public static final int CHAR = 2;
+
+ /**
+ * The sort of the byte type. See {@link #getSort getSort}.
+ */
+ public static final int BYTE = 3;
+
+ /**
+ * The sort of the short type. See {@link #getSort getSort}.
+ */
+ public static final int SHORT = 4;
+
+ /**
+ * The sort of the int type. See {@link #getSort getSort}.
+ */
+ public static final int INT = 5;
+
+ /**
+ * The sort of the float type. See {@link #getSort getSort}.
+ */
+ public static final int FLOAT = 6;
+
+ /**
+ * The sort of the long type. See {@link #getSort getSort}.
+ */
+ public static final int LONG = 7;
+
+ /**
+ * The sort of the double type. See {@link #getSort getSort}.
+ */
+ public static final int DOUBLE = 8;
+
+ /**
+ * The sort of array reference types. See {@link #getSort getSort}.
+ */
+ public static final int ARRAY = 9;
+
+ /**
+ * The sort of object reference types. See {@link #getSort getSort}.
+ */
+ public static final int OBJECT = 10;
+
+ /**
+ * The sort of method types. See {@link #getSort getSort}.
+ */
+ public static final int METHOD = 11;
+
+ /**
+ * The void type.
+ */
+ public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
+ | (5 << 16) | (0 << 8) | 0, 1);
+
+ /**
+ * The boolean type.
+ */
+ public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
+ | (0 << 16) | (5 << 8) | 1, 1);
+
+ /**
+ * The char type.
+ */
+ public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
+ | (0 << 16) | (6 << 8) | 1, 1);
+
+ /**
+ * The byte type.
+ */
+ public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
+ | (0 << 16) | (5 << 8) | 1, 1);
+
+ /**
+ * The short type.
+ */
+ public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
+ | (0 << 16) | (7 << 8) | 1, 1);
+
+ /**
+ * The int type.
+ */
+ public static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
+ | (0 << 16) | (0 << 8) | 1, 1);
+
+ /**
+ * The float type.
+ */
+ public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
+ | (2 << 16) | (2 << 8) | 1, 1);
+
+ /**
+ * The long type.
+ */
+ public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
+ | (1 << 16) | (1 << 8) | 2, 1);
+
+ /**
+ * The double type.
+ */
+ public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
+ | (3 << 16) | (3 << 8) | 2, 1);
+
+ // ------------------------------------------------------------------------
+ // Fields
+ // ------------------------------------------------------------------------
+
+ /**
+ * The sort of this Java type.
+ */
+ private final int sort;
+
+ /**
+ * A buffer containing the internal name of this Java type. This field is
+ * only used for reference types.
+ */
+ private final char[] buf;
+
+ /**
+ * The offset of the internal name of this Java type in {@link #buf buf} or,
+ * for primitive types, the size, descriptor and getOpcode offsets for this
+ * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
+ * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
+ */
+ private final int off;
+
+ /**
+ * The length of the internal name of this Java type.
+ */
+ private final int len;
+
+ // ------------------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a reference type.
+ *
+ * @param sort
+ * the sort of the reference type to be constructed.
+ * @param buf
+ * a buffer containing the descriptor of the previous type.
+ * @param off
+ * the offset of this descriptor in the previous buffer.
+ * @param len
+ * the length of this descriptor.
+ */
+ private Type(final int sort, final char[] buf, final int off, final int len) {
+ this.sort = sort;
+ this.buf = buf;
+ this.off = off;
+ this.len = len;
+ }
+
+ /**
+ * Returns the Java type corresponding to the given type descriptor.
+ *
+ * @param typeDescriptor
+ * a field or method type descriptor.
+ * @return the Java type corresponding to the given type descriptor.
+ */
+ public static Type getType(final String typeDescriptor) {
+ return getType(typeDescriptor.toCharArray(), 0);
+ }
+
+ /**
+ * Returns the Java type corresponding to the given internal name.
+ *
+ * @param internalName
+ * an internal name.
+ * @return the Java type corresponding to the given internal name.
+ */
+ public static Type getObjectType(final String internalName) {
+ char[] buf = internalName.toCharArray();
+ return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length);
+ }
+
+ /**
+ * Returns the Java type corresponding to the given method descriptor.
+ * Equivalent to Type.getType(methodDescriptor)
.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java type corresponding to the given method descriptor.
+ */
+ public static Type getMethodType(final String methodDescriptor) {
+ return getType(methodDescriptor.toCharArray(), 0);
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given argument and
+ * return types.
+ *
+ * @param returnType
+ * the return type of the method.
+ * @param argumentTypes
+ * the argument types of the method.
+ * @return the Java type corresponding to the given argument and return
+ * types.
+ */
+ public static Type getMethodType(final Type returnType,
+ final Type... argumentTypes) {
+ return getType(getMethodDescriptor(returnType, argumentTypes));
+ }
+
+ /**
+ * Returns the Java type corresponding to the given class.
+ *
+ * @param c
+ * a class.
+ * @return the Java type corresponding to the given class.
+ */
+ public static Type getType(final Class> c) {
+ if (c.isPrimitive()) {
+ if (c == Integer.TYPE) {
+ return INT_TYPE;
+ } else if (c == Void.TYPE) {
+ return VOID_TYPE;
+ } else if (c == Boolean.TYPE) {
+ return BOOLEAN_TYPE;
+ } else if (c == Byte.TYPE) {
+ return BYTE_TYPE;
+ } else if (c == Character.TYPE) {
+ return CHAR_TYPE;
+ } else if (c == Short.TYPE) {
+ return SHORT_TYPE;
+ } else if (c == Double.TYPE) {
+ return DOUBLE_TYPE;
+ } else if (c == Float.TYPE) {
+ return FLOAT_TYPE;
+ } else /* if (c == Long.TYPE) */{
+ return LONG_TYPE;
+ }
+ } else {
+ return getType(getDescriptor(c));
+ }
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given constructor.
+ *
+ * @param c
+ * a {@link Constructor Constructor} object.
+ * @return the Java method type corresponding to the given constructor.
+ */
+ public static Type getType(final Constructor> c) {
+ return getType(getConstructorDescriptor(c));
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given method.
+ *
+ * @param m
+ * a {@link Method Method} object.
+ * @return the Java method type corresponding to the given method.
+ */
+ public static Type getType(final Method m) {
+ return getType(getMethodDescriptor(m));
+ }
+
+ /**
+ * Returns the Java types corresponding to the argument types of the given
+ * method descriptor.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java types corresponding to the argument types of the given
+ * method descriptor.
+ */
+ public static Type[] getArgumentTypes(final String methodDescriptor) {
+ char[] buf = methodDescriptor.toCharArray();
+ int off = 1;
+ int size = 0;
+ while (true) {
+ char car = buf[off++];
+ if (car == ')') {
+ break;
+ } else if (car == 'L') {
+ while (buf[off++] != ';') {
+ }
+ ++size;
+ } else if (car != '[') {
+ ++size;
+ }
+ }
+ Type[] args = new Type[size];
+ off = 1;
+ size = 0;
+ while (buf[off] != ')') {
+ args[size] = getType(buf, off);
+ off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
+ size += 1;
+ }
+ return args;
+ }
+
+ /**
+ * Returns the Java types corresponding to the argument types of the given
+ * method.
+ *
+ * @param method
+ * a method.
+ * @return the Java types corresponding to the argument types of the given
+ * method.
+ */
+ public static Type[] getArgumentTypes(final Method method) {
+ Class>[] classes = method.getParameterTypes();
+ Type[] types = new Type[classes.length];
+ for (int i = classes.length - 1; i >= 0; --i) {
+ types[i] = getType(classes[i]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the Java type corresponding to the return type of the given
+ * method descriptor.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java type corresponding to the return type of the given
+ * method descriptor.
+ */
+ public static Type getReturnType(final String methodDescriptor) {
+ char[] buf = methodDescriptor.toCharArray();
+ return getType(buf, methodDescriptor.indexOf(')') + 1);
+ }
+
+ /**
+ * Returns the Java type corresponding to the return type of the given
+ * method.
+ *
+ * @param method
+ * a method.
+ * @return the Java type corresponding to the return type of the given
+ * method.
+ */
+ public static Type getReturnType(final Method method) {
+ return getType(method.getReturnType());
+ }
+
+ /**
+ * Computes the size of the arguments and of the return value of a method.
+ *
+ * @param desc
+ * the descriptor of a method.
+ * @return the size of the arguments of the method (plus one for the
+ * implicit this argument), argSize, and the size of its return
+ * value, retSize, packed into a single int i =
+ * (argSize << 2) | retSize (argSize is therefore equal to
+ * i >> 2 , and retSize to i & 0x03 ).
+ */
+ public static int getArgumentsAndReturnSizes(final String desc) {
+ int n = 1;
+ int c = 1;
+ while (true) {
+ char car = desc.charAt(c++);
+ if (car == ')') {
+ car = desc.charAt(c);
+ return n << 2
+ | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1));
+ } else if (car == 'L') {
+ while (desc.charAt(c++) != ';') {
+ }
+ n += 1;
+ } else if (car == '[') {
+ while ((car = desc.charAt(c)) == '[') {
+ ++c;
+ }
+ if (car == 'D' || car == 'J') {
+ n -= 1;
+ }
+ } else if (car == 'D' || car == 'J') {
+ n += 2;
+ } else {
+ n += 1;
+ }
+ }
+ }
+
+ /**
+ * Returns the Java type corresponding to the given type descriptor. For
+ * method descriptors, buf is supposed to contain nothing more than the
+ * descriptor itself.
+ *
+ * @param buf
+ * a buffer containing a type descriptor.
+ * @param off
+ * the offset of this descriptor in the previous buffer.
+ * @return the Java type corresponding to the given type descriptor.
+ */
+ private static Type getType(final char[] buf, final int off) {
+ int len;
+ switch (buf[off]) {
+ case 'V':
+ return VOID_TYPE;
+ case 'Z':
+ return BOOLEAN_TYPE;
+ case 'C':
+ return CHAR_TYPE;
+ case 'B':
+ return BYTE_TYPE;
+ case 'S':
+ return SHORT_TYPE;
+ case 'I':
+ return INT_TYPE;
+ case 'F':
+ return FLOAT_TYPE;
+ case 'J':
+ return LONG_TYPE;
+ case 'D':
+ return DOUBLE_TYPE;
+ case '[':
+ len = 1;
+ while (buf[off + len] == '[') {
+ ++len;
+ }
+ if (buf[off + len] == 'L') {
+ ++len;
+ while (buf[off + len] != ';') {
+ ++len;
+ }
+ }
+ return new Type(ARRAY, buf, off, len + 1);
+ case 'L':
+ len = 1;
+ while (buf[off + len] != ';') {
+ ++len;
+ }
+ return new Type(OBJECT, buf, off + 1, len - 1);
+ // case '(':
+ default:
+ return new Type(METHOD, buf, off, buf.length - off);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Accessors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the sort of this Java type.
+ *
+ * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR},
+ * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT},
+ * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE},
+ * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD
+ * METHOD}.
+ */
+ public int getSort() {
+ return sort;
+ }
+
+ /**
+ * Returns the number of dimensions of this array type. This method should
+ * only be used for an array type.
+ *
+ * @return the number of dimensions of this array type.
+ */
+ public int getDimensions() {
+ int i = 1;
+ while (buf[off + i] == '[') {
+ ++i;
+ }
+ return i;
+ }
+
+ /**
+ * Returns the type of the elements of this array type. This method should
+ * only be used for an array type.
+ *
+ * @return Returns the type of the elements of this array type.
+ */
+ public Type getElementType() {
+ return getType(buf, off + getDimensions());
+ }
+
+ /**
+ * Returns the binary name of the class corresponding to this type. This
+ * method must not be used on method types.
+ *
+ * @return the binary name of the class corresponding to this type.
+ */
+ public String getClassName() {
+ switch (sort) {
+ case VOID:
+ return "void";
+ case BOOLEAN:
+ return "boolean";
+ case CHAR:
+ return "char";
+ case BYTE:
+ return "byte";
+ case SHORT:
+ return "short";
+ case INT:
+ return "int";
+ case FLOAT:
+ return "float";
+ case LONG:
+ return "long";
+ case DOUBLE:
+ return "double";
+ case ARRAY:
+ StringBuilder sb = new StringBuilder(getElementType().getClassName());
+ for (int i = getDimensions(); i > 0; --i) {
+ sb.append("[]");
+ }
+ return sb.toString();
+ case OBJECT:
+ return new String(buf, off, len).replace('/', '.');
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the internal name of the class corresponding to this object or
+ * array type. The internal name of a class is its fully qualified name (as
+ * returned by Class.getName(), where '.' are replaced by '/'. This method
+ * should only be used for an object or array type.
+ *
+ * @return the internal name of the class corresponding to this object type.
+ */
+ public String getInternalName() {
+ return new String(buf, off, len);
+ }
+
+ /**
+ * Returns the argument types of methods of this type. This method should
+ * only be used for method types.
+ *
+ * @return the argument types of methods of this type.
+ */
+ public Type[] getArgumentTypes() {
+ return getArgumentTypes(getDescriptor());
+ }
+
+ /**
+ * Returns the return type of methods of this type. This method should only
+ * be used for method types.
+ *
+ * @return the return type of methods of this type.
+ */
+ public Type getReturnType() {
+ return getReturnType(getDescriptor());
+ }
+
+ /**
+ * Returns the size of the arguments and of the return value of methods of
+ * this type. This method should only be used for method types.
+ *
+ * @return the size of the arguments (plus one for the implicit this
+ * argument), argSize, and the size of the return value, retSize,
+ * packed into a single
+ * int i = (argSize << 2) | retSize
+ * (argSize is therefore equal to i >> 2 ,
+ * and retSize to i & 0x03 ).
+ */
+ public int getArgumentsAndReturnSizes() {
+ return getArgumentsAndReturnSizes(getDescriptor());
+ }
+
+ // ------------------------------------------------------------------------
+ // Conversion to type descriptors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the descriptor corresponding to this Java type.
+ *
+ * @return the descriptor corresponding to this Java type.
+ */
+ public String getDescriptor() {
+ StringBuffer buf = new StringBuffer();
+ getDescriptor(buf);
+ return buf.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given argument and return
+ * types.
+ *
+ * @param returnType
+ * the return type of the method.
+ * @param argumentTypes
+ * the argument types of the method.
+ * @return the descriptor corresponding to the given argument and return
+ * types.
+ */
+ public static String getMethodDescriptor(final Type returnType,
+ final Type... argumentTypes) {
+ StringBuffer buf = new StringBuffer();
+ buf.append('(');
+ for (int i = 0; i < argumentTypes.length; ++i) {
+ argumentTypes[i].getDescriptor(buf);
+ }
+ buf.append(')');
+ returnType.getDescriptor(buf);
+ return buf.toString();
+ }
+
+ /**
+ * Appends the descriptor corresponding to this Java type to the given
+ * string buffer.
+ *
+ * @param buf
+ * the string buffer to which the descriptor must be appended.
+ */
+ private void getDescriptor(final StringBuffer buf) {
+ if (this.buf == null) {
+ // descriptor is in byte 3 of 'off' for primitive types (buf ==
+ // null)
+ buf.append((char) ((off & 0xFF000000) >>> 24));
+ } else if (sort == OBJECT) {
+ buf.append('L');
+ buf.append(this.buf, off, len);
+ buf.append(';');
+ } else { // sort == ARRAY || sort == METHOD
+ buf.append(this.buf, off, len);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Direct conversion from classes to type descriptors,
+ // without intermediate Type objects
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the internal name of the given class. The internal name of a
+ * class is its fully qualified name, as returned by Class.getName(), where
+ * '.' are replaced by '/'.
+ *
+ * @param c
+ * an object or array class.
+ * @return the internal name of the given class.
+ */
+ public static String getInternalName(final Class> c) {
+ return c.getName().replace('.', '/');
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given Java type.
+ *
+ * @param c
+ * an object class, a primitive class or an array class.
+ * @return the descriptor corresponding to the given class.
+ */
+ public static String getDescriptor(final Class> c) {
+ StringBuffer buf = new StringBuffer();
+ getDescriptor(buf, c);
+ return buf.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given constructor.
+ *
+ * @param c
+ * a {@link Constructor Constructor} object.
+ * @return the descriptor of the given constructor.
+ */
+ public static String getConstructorDescriptor(final Constructor> c) {
+ Class>[] parameters = c.getParameterTypes();
+ StringBuffer buf = new StringBuffer();
+ buf.append('(');
+ for (int i = 0; i < parameters.length; ++i) {
+ getDescriptor(buf, parameters[i]);
+ }
+ return buf.append(")V").toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given method.
+ *
+ * @param m
+ * a {@link Method Method} object.
+ * @return the descriptor of the given method.
+ */
+ public static String getMethodDescriptor(final Method m) {
+ Class>[] parameters = m.getParameterTypes();
+ StringBuffer buf = new StringBuffer();
+ buf.append('(');
+ for (int i = 0; i < parameters.length; ++i) {
+ getDescriptor(buf, parameters[i]);
+ }
+ buf.append(')');
+ getDescriptor(buf, m.getReturnType());
+ return buf.toString();
+ }
+
+ /**
+ * Appends the descriptor of the given class to the given string buffer.
+ *
+ * @param buf
+ * the string buffer to which the descriptor must be appended.
+ * @param c
+ * the class whose descriptor must be computed.
+ */
+ private static void getDescriptor(final StringBuffer buf, final Class> c) {
+ Class> d = c;
+ while (true) {
+ if (d.isPrimitive()) {
+ char car;
+ if (d == Integer.TYPE) {
+ car = 'I';
+ } else if (d == Void.TYPE) {
+ car = 'V';
+ } else if (d == Boolean.TYPE) {
+ car = 'Z';
+ } else if (d == Byte.TYPE) {
+ car = 'B';
+ } else if (d == Character.TYPE) {
+ car = 'C';
+ } else if (d == Short.TYPE) {
+ car = 'S';
+ } else if (d == Double.TYPE) {
+ car = 'D';
+ } else if (d == Float.TYPE) {
+ car = 'F';
+ } else /* if (d == Long.TYPE) */{
+ car = 'J';
+ }
+ buf.append(car);
+ return;
+ } else if (d.isArray()) {
+ buf.append('[');
+ d = d.getComponentType();
+ } else {
+ buf.append('L');
+ String name = d.getName();
+ int len = name.length();
+ for (int i = 0; i < len; ++i) {
+ char car = name.charAt(i);
+ buf.append(car == '.' ? '/' : car);
+ }
+ buf.append(';');
+ return;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Corresponding size and opcodes
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of values of this type. This method must not be used for
+ * method types.
+ *
+ * @return the size of values of this type, i.e., 2 for long and
+ * double , 0 for void and 1 otherwise.
+ */
+ public int getSize() {
+ // the size is in byte 0 of 'off' for primitive types (buf == null)
+ return buf == null ? (off & 0xFF) : 1;
+ }
+
+ /**
+ * Returns a JVM instruction opcode adapted to this Java type. This method
+ * must not be used for method types.
+ *
+ * @param opcode
+ * a JVM instruction opcode. This opcode must be one of ILOAD,
+ * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG,
+ * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
+ * @return an opcode that is similar to the given opcode, but adapted to
+ * this Java type. For example, if this type is float and
+ * opcode is IRETURN, this method returns FRETURN.
+ */
+ public int getOpcode(final int opcode) {
+ if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
+ // the offset for IALOAD or IASTORE is in byte 1 of 'off' for
+ // primitive types (buf == null)
+ return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4);
+ } else {
+ // the offset for other instructions is in byte 2 of 'off' for
+ // primitive types (buf == null)
+ return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Equals, hashCode and toString
+ // ------------------------------------------------------------------------
+
+ /**
+ * Tests if the given object is equal to this type.
+ *
+ * @param o
+ * the object to be compared to this type.
+ * @return true if the given object is equal to this type.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Type)) {
+ return false;
+ }
+ Type t = (Type) o;
+ if (sort != t.sort) {
+ return false;
+ }
+ if (sort >= ARRAY) {
+ if (len != t.len) {
+ return false;
+ }
+ for (int i = off, j = t.off, end = i + len; i < end; i++, j++) {
+ if (buf[i] != t.buf[j]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code value for this type.
+ *
+ * @return a hash code value for this type.
+ */
+ @Override
+ public int hashCode() {
+ int hc = 13 * sort;
+ if (sort >= ARRAY) {
+ for (int i = off, end = i + len; i < end; i++) {
+ hc = 17 * (hc + buf[i]);
+ }
+ }
+ return hc;
+ }
+
+ /**
+ * Returns a string representation of this type.
+ *
+ * @return the descriptor of this type.
+ */
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
+
+ public char[] getBuf() {
+ return buf;
+ }
+
+ public int getOff() {
+ return off;
+ }
+
+ public int getLen() {
+ return len;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/objectweb/asm/TypePath.java b/src/main/java/org/objectweb/asm/TypePath.java
new file mode 100644
index 00000000..b2b32325
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/TypePath.java
@@ -0,0 +1,196 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2013 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.objectweb.asm;
+
+/**
+ * The path to a type argument, wildcard bound, array element type, or static
+ * inner type within an enclosing type.
+ *
+ * @author Eric Bruneton
+ */
+public class TypePath {
+
+ /**
+ * A type path step that steps into the element type of an array type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int ARRAY_ELEMENT = 0;
+
+ /**
+ * A type path step that steps into the nested type of a class type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int INNER_TYPE = 1;
+
+ /**
+ * A type path step that steps into the bound of a wildcard type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int WILDCARD_BOUND = 2;
+
+ /**
+ * A type path step that steps into a type argument of a generic type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int TYPE_ARGUMENT = 3;
+
+ /**
+ * The byte array where the path is stored, in Java class file format.
+ */
+ byte[] b;
+
+ /**
+ * The offset of the first byte of the type path in 'b'.
+ */
+ int offset;
+
+ /**
+ * Creates a new type path.
+ *
+ * @param b
+ * the byte array containing the type path in Java class file
+ * format.
+ * @param offset
+ * the offset of the first byte of the type path in 'b'.
+ */
+ TypePath(byte[] b, int offset) {
+ this.b = b;
+ this.offset = offset;
+ }
+
+ /**
+ * Returns the length of this path.
+ *
+ * @return the length of this path.
+ */
+ public int getLength() {
+ return b[offset];
+ }
+
+ /**
+ * Returns the value of the given step of this path.
+ *
+ * @param index
+ * an index between 0 and {@link #getLength()}, exclusive.
+ * @return {@link #ARRAY_ELEMENT ARRAY_ELEMENT}, {@link #INNER_TYPE
+ * INNER_TYPE}, {@link #WILDCARD_BOUND WILDCARD_BOUND}, or
+ * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}.
+ */
+ public int getStep(int index) {
+ return b[offset + 2 * index + 1];
+ }
+
+ /**
+ * Returns the index of the type argument that the given step is stepping
+ * into. This method should only be used for steps whose value is
+ * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}.
+ *
+ * @param index
+ * an index between 0 and {@link #getLength()}, exclusive.
+ * @return the index of the type argument that the given step is stepping
+ * into.
+ */
+ public int getStepArgument(int index) {
+ return b[offset + 2 * index + 2];
+ }
+
+ /**
+ * Converts a type path in string form, in the format used by
+ * {@link #toString()}, into a TypePath object.
+ *
+ * @param typePath
+ * a type path in string form, in the format used by
+ * {@link #toString()}. May be null or empty.
+ * @return the corresponding TypePath object, or null if the path is empty.
+ */
+ public static TypePath fromString(final String typePath) {
+ if (typePath == null || typePath.length() == 0) {
+ return null;
+ }
+ int n = typePath.length();
+ ByteVector out = new ByteVector(n);
+ out.putByte(0);
+ for (int i = 0; i < n;) {
+ char c = typePath.charAt(i++);
+ if (c == '[') {
+ out.put11(ARRAY_ELEMENT, 0);
+ } else if (c == '.') {
+ out.put11(INNER_TYPE, 0);
+ } else if (c == '*') {
+ out.put11(WILDCARD_BOUND, 0);
+ } else if (c >= '0' && c <= '9') {
+ int typeArg = c - '0';
+ while (i < n && (c = typePath.charAt(i)) >= '0' && c <= '9') {
+ typeArg = typeArg * 10 + c - '0';
+ i += 1;
+ }
+ if (i < n && typePath.charAt(i) == ';') {
+ i += 1;
+ }
+ out.put11(TYPE_ARGUMENT, typeArg);
+ }
+ }
+ out.data[0] = (byte) (out.length / 2);
+ return new TypePath(out.data, 0);
+ }
+
+ /**
+ * Returns a string representation of this type path. {@link #ARRAY_ELEMENT
+ * ARRAY_ELEMENT} steps are represented with '[', {@link #INNER_TYPE
+ * INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND WILDCARD_BOUND} steps
+ * with '*' and {@link #TYPE_ARGUMENT TYPE_ARGUMENT} steps with their type
+ * argument index in decimal form followed by ';'.
+ */
+ @Override
+ public String toString() {
+ int length = getLength();
+ StringBuilder result = new StringBuilder(length * 2);
+ for (int i = 0; i < length; ++i) {
+ switch (getStep(i)) {
+ case ARRAY_ELEMENT:
+ result.append('[');
+ break;
+ case INNER_TYPE:
+ result.append('.');
+ break;
+ case WILDCARD_BOUND:
+ result.append('*');
+ break;
+ case TYPE_ARGUMENT:
+ result.append(getStepArgument(i)).append(';');
+ break;
+ default:
+ result.append('_');
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/TypeReference.java b/src/main/java/org/objectweb/asm/TypeReference.java
new file mode 100644
index 00000000..2654b5d6
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/TypeReference.java
@@ -0,0 +1,452 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2013 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.objectweb.asm;
+
+/**
+ * A reference to a type appearing in a class, field or method declaration, or
+ * on an instruction. Such a reference designates the part of the class where
+ * the referenced type is appearing (e.g. an 'extends', 'implements' or 'throws'
+ * clause, a 'new' instruction, a 'catch' clause, a type cast, a local variable
+ * declaration, etc).
+ *
+ * @author Eric Bruneton
+ */
+public class TypeReference {
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * class. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_TYPE_PARAMETER = 0x00;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_TYPE_PARAMETER = 0x01;
+
+ /**
+ * The sort of type references that target the super class of a class or one
+ * of the interfaces it implements. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_EXTENDS = 0x10;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a
+ * generic class. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_TYPE_PARAMETER_BOUND = 0x11;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a
+ * generic method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_TYPE_PARAMETER_BOUND = 0x12;
+
+ /**
+ * The sort of type references that target the type of a field. See
+ * {@link #getSort getSort}.
+ */
+ public final static int FIELD = 0x13;
+
+ /**
+ * The sort of type references that target the return type of a method. See
+ * {@link #getSort getSort}.
+ */
+ public final static int METHOD_RETURN = 0x14;
+
+ /**
+ * The sort of type references that target the receiver type of a method.
+ * See {@link #getSort getSort}.
+ */
+ public final static int METHOD_RECEIVER = 0x15;
+
+ /**
+ * The sort of type references that target the type of a formal parameter of
+ * a method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_FORMAL_PARAMETER = 0x16;
+
+ /**
+ * The sort of type references that target the type of an exception declared
+ * in the throws clause of a method. See {@link #getSort getSort}.
+ */
+ public final static int THROWS = 0x17;
+
+ /**
+ * The sort of type references that target the type of a local variable in a
+ * method. See {@link #getSort getSort}.
+ */
+ public final static int LOCAL_VARIABLE = 0x40;
+
+ /**
+ * The sort of type references that target the type of a resource variable
+ * in a method. See {@link #getSort getSort}.
+ */
+ public final static int RESOURCE_VARIABLE = 0x41;
+
+ /**
+ * The sort of type references that target the type of the exception of a
+ * 'catch' clause in a method. See {@link #getSort getSort}.
+ */
+ public final static int EXCEPTION_PARAMETER = 0x42;
+
+ /**
+ * The sort of type references that target the type declared in an
+ * 'instanceof' instruction. See {@link #getSort getSort}.
+ */
+ public final static int INSTANCEOF = 0x43;
+
+ /**
+ * The sort of type references that target the type of the object created by
+ * a 'new' instruction. See {@link #getSort getSort}.
+ */
+ public final static int NEW = 0x44;
+
+ /**
+ * The sort of type references that target the receiver type of a
+ * constructor reference. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_REFERENCE = 0x45;
+
+ /**
+ * The sort of type references that target the receiver type of a method
+ * reference. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_REFERENCE = 0x46;
+
+ /**
+ * The sort of type references that target the type declared in an explicit
+ * or implicit cast instruction. See {@link #getSort getSort}.
+ */
+ public final static int CAST = 0x47;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * constructor in a constructor call. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method in a method call. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * constructor in a constructor reference. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method in a method reference. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B;
+
+ /**
+ * The type reference value in Java class file format.
+ */
+ private int value;
+
+ /**
+ * Creates a new TypeReference.
+ *
+ * @param typeRef
+ * the int encoded value of the type reference, as received in a
+ * visit method related to type annotations, like
+ * visitTypeAnnotation.
+ */
+ public TypeReference(int typeRef) {
+ this.value = typeRef;
+ }
+
+ /**
+ * Returns a type reference of the given sort.
+ *
+ * @param sort
+ * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN},
+ * {@link #METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link #LOCAL_VARIABLE LOCAL_VARIABLE},
+ * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE},
+ * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW},
+ * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, or
+ * {@link #METHOD_REFERENCE METHOD_REFERENCE}.
+ * @return a type reference of the given sort.
+ */
+ public static TypeReference newTypeReference(int sort) {
+ return new TypeReference(sort << 24);
+ }
+
+ /**
+ * Returns a reference to a type parameter of a generic class or method.
+ *
+ * @param sort
+ * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}.
+ * @param paramIndex
+ * the type parameter index.
+ * @return a reference to the given generic class or method type parameter.
+ */
+ public static TypeReference newTypeParameterReference(int sort,
+ int paramIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to a type parameter bound of a generic class or
+ * method.
+ *
+ * @param sort
+ * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}.
+ * @param paramIndex
+ * the type parameter index.
+ * @param boundIndex
+ * the type bound index within the above type parameters.
+ * @return a reference to the given generic class or method type parameter
+ * bound.
+ */
+ public static TypeReference newTypeParameterBoundReference(int sort,
+ int paramIndex, int boundIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16)
+ | (boundIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the super class or to an interface of the
+ * 'implements' clause of a class.
+ *
+ * @param itfIndex
+ * the index of an interface in the 'implements' clause of a
+ * class, or -1 to reference the super class of the class.
+ * @return a reference to the given super type of a class.
+ */
+ public static TypeReference newSuperTypeReference(int itfIndex) {
+ itfIndex &= 0xFFFF;
+ return new TypeReference((CLASS_EXTENDS << 24) | (itfIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a formal parameter of a method.
+ *
+ * @param paramIndex
+ * the formal parameter index.
+ *
+ * @return a reference to the type of the given method formal parameter.
+ */
+ public static TypeReference newFormalParameterReference(int paramIndex) {
+ return new TypeReference((METHOD_FORMAL_PARAMETER << 24)
+ | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to the type of an exception, in a 'throws' clause of
+ * a method.
+ *
+ * @param exceptionIndex
+ * the index of an exception in a 'throws' clause of a method.
+ *
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newExceptionReference(int exceptionIndex) {
+ return new TypeReference((THROWS << 24) | (exceptionIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of the exception declared in a 'catch'
+ * clause of a method.
+ *
+ * @param tryCatchBlockIndex
+ * the index of a try catch block (using the order in which they
+ * are visited with visitTryCatchBlock).
+ *
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newTryCatchReference(int tryCatchBlockIndex) {
+ return new TypeReference((EXCEPTION_PARAMETER << 24)
+ | (tryCatchBlockIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a type argument in a constructor or
+ * method call or reference.
+ *
+ * @param sort
+ * {@link #CAST CAST},
+ * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}.
+ * @param argIndex
+ * the type argument index.
+ *
+ * @return a reference to the type of the given type argument.
+ */
+ public static TypeReference newTypeArgumentReference(int sort, int argIndex) {
+ return new TypeReference((sort << 24) | argIndex);
+ }
+
+ /**
+ * Returns the sort of this type reference.
+ *
+ * @return {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER},
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER},
+ * {@link #CLASS_EXTENDS CLASS_EXTENDS},
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND},
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND},
+ * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN},
+ * {@link #METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER},
+ * {@link #THROWS THROWS}, {@link #LOCAL_VARIABLE LOCAL_VARIABLE},
+ * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE},
+ * {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER},
+ * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW},
+ * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE},
+ * {@link #METHOD_REFERENCE METHOD_REFERENCE}, {@link #CAST CAST},
+ * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}.
+ */
+ public int getSort() {
+ return value >>> 24;
+ }
+
+ /**
+ * Returns the index of the type parameter referenced by this type
+ * reference. This method must only be used for type references whose sort
+ * is {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER},
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER},
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeParameterIndex() {
+ return (value & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the type parameter bound, within the type parameter
+ * {@link #getTypeParameterIndex}, referenced by this type reference. This
+ * method must only be used for type references whose sort is
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter bound index.
+ */
+ public int getTypeParameterBoundIndex() {
+ return (value & 0x0000FF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the "super type" of a class that is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #CLASS_EXTENDS CLASS_EXTENDS}.
+ *
+ * @return the index of an interface in the 'implements' clause of a class,
+ * or -1 if this type reference references the type of the super
+ * class.
+ */
+ public int getSuperTypeIndex() {
+ return (short) ((value & 0x00FFFF00) >> 8);
+ }
+
+ /**
+ * Returns the index of the formal parameter whose type is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}.
+ *
+ * @return a formal parameter index.
+ */
+ public int getFormalParameterIndex() {
+ return (value & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the exception, in a 'throws' clause of a method,
+ * whose type is referenced by this type reference. This method must only be
+ * used for type references whose sort is {@link #THROWS THROWS}.
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getExceptionIndex() {
+ return (value & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the try catch block (using the order in which they
+ * are visited with visitTryCatchBlock), whose 'catch' type is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER} .
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getTryCatchBlockIndex() {
+ return (value & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the type argument referenced by this type reference.
+ * This method must only be used for type references whose sort is
+ * {@link #CAST CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeArgumentIndex() {
+ return value & 0xFF;
+ }
+
+ /**
+ * Returns the int encoded value of this type reference, suitable for use in
+ * visit methods related to type annotations, like visitTypeAnnotation.
+ *
+ * @return the int encoded value of this type reference.
+ */
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/org/objectweb/asm/commons/AdviceAdapter.java b/src/main/java/org/objectweb/asm/commons/AdviceAdapter.java
new file mode 100644
index 00000000..ca31ede9
--- /dev/null
+++ b/src/main/java/org/objectweb/asm/commons/AdviceAdapter.java
@@ -0,0 +1,646 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.objectweb.asm.commons;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * A {@link org.objectweb.asm.MethodVisitor} to insert before, after and around
+ * advices in methods and constructors.
+ *
+ * The behavior for constructors is like this:
+ *
+ *
+ * as long as the INVOKESPECIAL for the object initialization has not been
+ * reached, every bytecode instruction is dispatched in the ctor code visitor
+ *
+ * when this one is reached, it is only added in the ctor code visitor and a
+ * JP invoke is added
+ *
+ * after that, only the other code visitor receives the instructions
+ *
+ *
+ *
+ * @author Eugene Kuleshov
+ * @author Eric Bruneton
+ */
+public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
+
+ private static final Object THIS = new Object();
+
+ private static final Object OTHER = new Object();
+
+ protected int methodAccess;
+
+ protected String methodDesc;
+
+ private boolean constructor;
+
+ private boolean superInitialized;
+
+ private List stackFrame;
+
+ private Map> branches;
+
+ /**
+ * Creates a new {@link AdviceAdapter}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param mv
+ * the method visitor to which this adapter delegates calls.
+ * @param access
+ * the method's access flags (see {@link Opcodes}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ */
+ protected AdviceAdapter(final int api, final MethodVisitor mv,
+ final int access, final String name, final String desc) {
+ super(api, mv, access, name, desc);
+ methodAccess = access;
+ methodDesc = desc;
+ constructor = "".equals(name);
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitCode();
+ if (constructor) {
+ stackFrame = new ArrayList