Completed SHL-1

Work in progress for SHL-2, SHL-3.
This commit is contained in:
Mark Pollack
2012-03-13 19:52:17 -04:00
parent 833c787403
commit f2debb62f4
195 changed files with 22089 additions and 22 deletions

View File

@@ -0,0 +1,488 @@
package org.springframework.roo.shell;
import static org.springframework.roo.support.util.StringUtils.LINE_SEPARATOR;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import org.springframework.roo.shell.event.AbstractShellStatusPublisher;
import org.springframework.roo.shell.event.ShellStatus;
import org.springframework.roo.shell.event.ShellStatus.Status;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.IOUtils;
import org.springframework.roo.support.util.MathUtils;
import org.springframework.roo.support.util.StringUtils;
/**
* Provides a base {@link Shell} implementation.
*
* @author Ben Alex
*/
public abstract class AbstractShell extends AbstractShellStatusPublisher implements Shell {
// Constants
private static final String MY_SLOT = AbstractShell.class.getName();
//TODO Abstract out to make configurable.
protected static final String ROO_PROMPT = "spring> ";
// Public static fields; don't rename, make final, or make non-public, as
// they are part of the public API, e.g. are changed by STS.
public static String completionKeys = "TAB";
public static String shellPrompt = ROO_PROMPT;
// Instance fields
protected final Logger logger = HandlerUtils.getLogger(getClass());
protected boolean inBlockComment;
protected ExitShellRequest exitShellRequest;
/**
* Returns any classpath resources with the given path
*
* @param path the path for which to search (never null)
* @return <code>null</code> if the search can't be performed
* @since 1.2.0
*/
protected abstract Collection<URL> findResources(String path);
protected abstract String getHomeAsString();
protected abstract ExecutionStrategy getExecutionStrategy();
protected abstract Parser getParser();
@CliCommand(value = { "script" }, help = "Parses the specified resource file and executes its commands")
public void script(
@CliOption(key = { "", "file" }, help = "The file to locate and execute", mandatory = true) final File script,
@CliOption(key = "lineNumbers", mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = "Display line numbers when executing the script") final boolean lineNumbers) {
Assert.notNull(script, "Script file to parse is required");
double startedNanoseconds = System.nanoTime();
final InputStream inputStream = openScript(script);
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(inputStream));
String line;
int i = 0;
while ((line = in.readLine()) != null) {
i++;
if (lineNumbers) {
logger.fine("Line " + i + ": " + line);
} else {
logger.fine(line);
}
if (!"".equals(line.trim())) {
boolean success = executeScriptLine(line);
if (success && ((line.trim().startsWith("q") || line.trim().startsWith("ex")))) {
break;
} else if (!success) {
// Abort script processing, given something went wrong
throw new IllegalStateException("Script execution aborted");
}
}
}
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
IOUtils.closeQuietly(inputStream, in);
double executionDurationInSeconds = (System.nanoTime() - startedNanoseconds) / 1000000000D;
logger.fine("Script required " + MathUtils.round(executionDurationInSeconds, 3) + " seconds to execute");
}
}
/**
* Opens the given script for reading
*
* @param script the script to read (required)
* @return a non-<code>null</code> input stream
*/
private InputStream openScript(final File script) {
try {
return new BufferedInputStream(new FileInputStream(script));
} catch (final FileNotFoundException fnfe) {
// Try to find the script via the classloader
final Collection<URL> urls = findResources(script.getName());
// Handle search failure
Assert.notNull(urls, "Unexpected error looking for '" + script.getName() + "'");
// Handle the search being OK but the file simply not being present
Assert.notEmpty(urls, "Script '" + script + "' not found on disk or in classpath");
Assert.isTrue(urls.size() == 1, "More than one '" + script + "' was found in the classpath; unable to continue");
try {
return urls.iterator().next().openStream();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* Execute the single line from a script.
* <p>
* This method can be overridden by sub-classes to pre-process script lines.
*/
protected boolean executeScriptLine(final String line) {
return executeCommand(line);
}
public boolean executeCommand(String line) {
// Another command was attempted
setShellStatus(ShellStatus.Status.PARSING);
final ExecutionStrategy executionStrategy = getExecutionStrategy();
boolean flashedMessage = false;
while (executionStrategy == null || !executionStrategy.isReadyForCommands()) {
// Wait
try {
Thread.sleep(500);
} catch (InterruptedException ignore) {}
if (!flashedMessage) {
flash(Level.INFO, "Please wait - still loading", MY_SLOT);
flashedMessage = true;
}
}
if (flashedMessage) {
flash(Level.INFO, "", MY_SLOT);
}
ParseResult parseResult = null;
try {
// We support simple block comments; ie a single pair per line
if (!inBlockComment && line.contains("/*") && line.contains("*/")) {
blockCommentBegin();
String lhs = line.substring(0, line.lastIndexOf("/*"));
if (line.contains("*/")) {
line = lhs + line.substring(line.lastIndexOf("*/") + 2);
blockCommentFinish();
} else {
line = lhs;
}
}
if (inBlockComment) {
if (!line.contains("*/")) {
return true;
}
blockCommentFinish();
line = line.substring(line.lastIndexOf("*/") + 2);
}
// We also support inline comments (but only at start of line, otherwise valid
// command options like http://www.helloworld.com will fail as per ROO-517)
if (!inBlockComment && (line.trim().startsWith("//") || line.trim().startsWith("#"))) { // # support in ROO-1116
line = "";
}
// Convert any TAB characters to whitespace (ROO-527)
line = line.replace('\t', ' ');
if ("".equals(line.trim())) {
setShellStatus(Status.EXECUTION_SUCCESS);
return true;
}
parseResult = getParser().parse(line);
if (parseResult == null) {
return false;
}
setShellStatus(Status.EXECUTING);
Object result = executionStrategy.execute(parseResult);
setShellStatus(Status.EXECUTION_RESULT_PROCESSING);
if (result != null) {
if (result instanceof ExitShellRequest) {
exitShellRequest = (ExitShellRequest) result;
// Give ProcessManager a chance to close down its threads before the overall OSGi framework is terminated (ROO-1938)
executionStrategy.terminate();
} else if (result instanceof Iterable<?>) {
for (Object o : (Iterable<?>) result) {
logger.info(o.toString());
}
} else {
logger.info(result.toString());
}
}
logCommandIfRequired(line, true);
setShellStatus(Status.EXECUTION_SUCCESS, line, parseResult);
return true;
} catch (RuntimeException e) {
setShellStatus(Status.EXECUTION_FAILED, line, parseResult);
// We rely on execution strategy to log it
try {
logCommandIfRequired(line, false);
} catch (Exception ignored) {}
return false;
} finally {
setShellStatus(Status.USER_INPUT);
}
}
/**
* Allows a subclass to log the execution of a well-formed command. This is invoked after a command
* has completed, and indicates whether the command returned normally or returned an exception. Note
* that attempted commands that are not well-formed (eg they are missing a mandatory argument) will
* never be presented to this method, as the command execution is never actually attempted in those
* cases. This method is only invoked if an attempt is made to execute a particular command.
*
* <p>
* Implementations should consider specially handling the "script" commands, and also
* indicating whether a command was successful or not. Implementations that wish to behave
* consistently with other {@link AbstractShell} subclasses are encouraged to simply override
* {@link #logCommandToOutput(String)} instead, and only override this method if you actually
* need to fine-tune the output logic.
*
* @param line the parsed line (any comments have been removed; never null)
* @param successful if the command was successful or not
*/
protected void logCommandIfRequired(final String line, final boolean successful) {
if (line.startsWith("script")) {
logCommandToOutput((successful ? "// " : "// [failed] ") + line);
} else {
logCommandToOutput((successful ? "" : "// [failed] ") + line);
}
}
/**
* Allows a subclass to actually write the resulting logged command to some form of output. This
* frees subclasses from needing to implement the logic within {@link #logCommandIfRequired(String, boolean)}.
*
* <p>
* Implementations should invoke {@link #getExitShellRequest()} to monitor any attempts to exit the shell and
* release resources such as output log files.
*
* @param processedLine the line that should be appended to some type of output (excluding the \n character)
*/
protected void logCommandToOutput(final String processedLine) {}
/**
* Base implementation of the {@link Shell#setPromptPath(String)} method, designed for simple shell
* implementations. Advanced implementations (eg those that support ANSI codes etc) will likely want
* to override this method and set the {@link #shellPrompt} variable directly.
*
* @param path to set (can be null or empty; must NOT be formatted in any special way eg ANSI codes)
*/
public void setPromptPath(final String path) {
if (path == null || "".equals(path)) {
shellPrompt = ROO_PROMPT;
} else {
shellPrompt = path + " " + ROO_PROMPT;
}
}
/**
* Default implementation of {@link Shell#setPromptPath(String, boolean))} method to satisfy STS compatibility.
*
* @param path to set (can be null or empty)
* @param overrideStyle
*/
public void setPromptPath(String path, boolean overrideStyle) {
setPromptPath(path);
}
public ExitShellRequest getExitShellRequest() {
return exitShellRequest;
}
@CliCommand(value = { "//", ";" }, help = "Inline comment markers (start of line only)")
public void inlineComment() {}
@CliCommand(value = { "/*" }, help = "Start of block comment")
public void blockCommentBegin() {
Assert.isTrue(!inBlockComment, "Cannot open a new block comment when one already active");
inBlockComment = true;
}
@CliCommand(value = { "*/" }, help = "End of block comment")
public void blockCommentFinish() {
Assert.isTrue(inBlockComment, "Cannot close a block comment when it has not been opened");
inBlockComment = false;
}
@CliCommand(value = { "system properties" }, help = "Shows the shell's properties")
public String props() {
final Set<String> data = new TreeSet<String>(); // For repeatability
for (final Entry<Object, Object> entry : System.getProperties().entrySet()) {
data.add(entry.getKey() + " = " + entry.getValue());
}
return StringUtils.collectionToDelimitedString(data, LINE_SEPARATOR) + LINE_SEPARATOR;
}
@CliCommand(value = { "date" }, help = "Displays the local date and time")
public String date() {
return DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date());
}
@CliCommand(value = { "flash test" }, help = "Tests message flashing")
public void flashCustom() throws Exception {
flash(Level.FINE, "Hello world", "a");
Thread.sleep(150);
flash(Level.FINE, "Short world", "a");
Thread.sleep(150);
flash(Level.FINE, "Small", "a");
Thread.sleep(150);
flash(Level.FINE, "Downloading xyz", "b");
Thread.sleep(150);
flash(Level.FINE, "", "a");
Thread.sleep(150);
flash(Level.FINE, "Downloaded xyz", "b");
Thread.sleep(150);
flash(Level.FINE, "System online", "c");
Thread.sleep(150);
flash(Level.FINE, "System ready", "c");
Thread.sleep(150);
flash(Level.FINE, "System farewell", "c");
Thread.sleep(150);
flash(Level.FINE, "", "c");
Thread.sleep(150);
flash(Level.FINE, "", "b");
}
@CliCommand(value = { "version" }, help = "Displays shell version")
public String version(@CliOption(key = "", help = "Special version flags") final String extra) {
StringBuilder sb = new StringBuilder();
if ("jaime".equals(extra)) {
sb.append(" /\\ /l").append(LINE_SEPARATOR);
sb.append(" ((.Y(!").append(LINE_SEPARATOR);
sb.append(" \\ |/").append(LINE_SEPARATOR);
sb.append(" / 6~6,").append(LINE_SEPARATOR);
sb.append(" \\ _ +-.").append(LINE_SEPARATOR);
sb.append(" \\`-=--^-' \\").append(LINE_SEPARATOR);
sb.append(" \\ \\ |\\--------------------------+").append(LINE_SEPARATOR);
sb.append(" _/ \\ | Thanks for loading Roo! |").append(LINE_SEPARATOR);
sb.append(" ( . Y +---------------------------+").append(LINE_SEPARATOR);
sb.append(" /\"\\ `---^--v---.").append(LINE_SEPARATOR);
sb.append(" / _ `---\"T~~\\/~\\/").append(LINE_SEPARATOR);
sb.append(" / \" ~\\. !").append(LINE_SEPARATOR);
sb.append(" _ Y Y.~~~ /'").append(LINE_SEPARATOR);
sb.append(" Y^| | | Roo 7").append(LINE_SEPARATOR);
sb.append(" | l | / . /'").append(LINE_SEPARATOR);
sb.append(" | `L | Y .^/ ~T").append(LINE_SEPARATOR);
sb.append(" | l ! | |/ | | ____ ____ ____").append(LINE_SEPARATOR);
sb.append(" | .`\\/' | Y | ! / __ \\/ __ \\/ __ \\").append(LINE_SEPARATOR);
sb.append(" l \"~ j l j L______ / /_/ / / / / / / /").append(LINE_SEPARATOR);
sb.append(" \\,____{ __\"\" ~ __ ,\\_,\\_ / _, _/ /_/ / /_/ /").append(LINE_SEPARATOR);
sb.append(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~ /_/ |_|\\____/\\____/").append(" ").append(versionInfo()).append(LINE_SEPARATOR);
return sb.toString();
}
sb.append(" ____ ____ ____ ").append(LINE_SEPARATOR);
sb.append(" / __ \\/ __ \\/ __ \\ ").append(LINE_SEPARATOR);
sb.append(" / /_/ / / / / / / / ").append(LINE_SEPARATOR);
sb.append(" / _, _/ /_/ / /_/ / ").append(LINE_SEPARATOR);
sb.append("/_/ |_|\\____/\\____/ ").append(" ").append(versionInfo()).append(LINE_SEPARATOR);
sb.append(LINE_SEPARATOR);
return sb.toString();
}
public static String versionInfo() {
// Try to determine the bundle version
String bundleVersion = null;
String gitCommitHash = null;
JarFile jarFile = null;
try {
URL classContainer = AbstractShell.class.getProtectionDomain().getCodeSource().getLocation();
if (classContainer.toString().endsWith(".jar")) {
// Attempt to obtain the "Bundle-Version" version from the manifest
jarFile = new JarFile(new File(classContainer.toURI()), false);
ZipEntry manifestEntry = jarFile.getEntry("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(jarFile.getInputStream(manifestEntry));
bundleVersion = manifest.getMainAttributes().getValue("Bundle-Version");
gitCommitHash = manifest.getMainAttributes().getValue("Git-Commit-Hash");
}
} catch (IOException ignoreAndMoveOn) {
} catch (URISyntaxException ignoreAndMoveOn) {
} finally {
IOUtils.closeQuietly(jarFile);
}
StringBuilder sb = new StringBuilder();
if (bundleVersion != null) {
sb.append(bundleVersion);
}
if (gitCommitHash != null && gitCommitHash.length() > 7) {
if (sb.length() > 0) {
sb.append(" "); // to separate from version
}
sb.append("[rev ");
sb.append(gitCommitHash.substring(0,7));
sb.append("]");
}
if (sb.length() == 0) {
sb.append("UNKNOWN VERSION");
}
return sb.toString();
}
public String getShellPrompt() {
return shellPrompt;
}
/**
* Obtains the home directory for the current shell instance.
*
* <p>
* Note: calls the {@link #getHomeAsString()} method to allow subclasses to provide the home directory location as
* string using different environment-specific strategies.
*
* <p>
* If the path indicated by {@link #getHomeAsString()} exists and refers to a directory, that directory
* is returned.
*
* <p>
* If the path indicated by {@link #getHomeAsString()} exists and refers to a file, an exception is thrown.
*
* <p>
* If the path indicated by {@link #getHomeAsString()} does not exist, it will be created as a directory.
* If this fails, an exception will be thrown.
*
* @return the home directory for the current shell instance (which is guaranteed to exist and be a directory)
*/
public File getHome() {
String rooHome = getHomeAsString();
File f = new File(rooHome);
Assert.isTrue(!f.exists() || (f.exists() && f.isDirectory()), "Path '" + f.getAbsolutePath() + "' must be a directory, or it must not exist");
if (!f.exists()) {
f.mkdirs();
}
Assert.isTrue(f.exists() && f.isDirectory(), "Path '" + f.getAbsolutePath() + "' is not a directory; please specify roo.home system property correctly");
return f;
}
/**
* Simple implementation of {@link #flash(Level, String, String)} that simply displays the message via the logger. It is
* strongly recommended shell implementations override this method with a more effective approach.
*/
public void flash(final Level level, final String message, final String slot) {
Assert.notNull(level, "Level is required for a flash message");
Assert.notNull(message, "Message is required for a flash message");
Assert.hasText(slot, "Slot name must be specified for a flash message");
if (!("".equals(message))) {
logger.log(level, message);
}
}
}

View File

@@ -0,0 +1,37 @@
package org.springframework.roo.shell;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotates a method that can indicate whether a particular command is presently
* available or not.
*
* <p>
* This annotation must only be applied to a public no-argument method that returns primitive boolean.
* The method should be inexpensive to evaluate, as this method can be called very
* frequently. If expensive operations are necessary to compute command availability,
* it is suggested the method return a boolean field that is maintained using the observer
* pattern.
*
* <p>
* It is possible that a particular availability method might be able to represent the
* availability status of multiple commands. As such, an availability indicator annotation
* will indicate the commands that it applies to. If a specific command has multiple
* aliases (ie by using an array for {@link CliCommand#value()}), only one of the commands
* need to be specified in the {@link CliAvailabilityIndicator} annotation.
*
* @author Ben Alex
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CliAvailabilityIndicator {
/**
* @return the name of the command or commands that this availability indicator represents
*/
String[] value();
}

View File

@@ -0,0 +1,22 @@
package org.springframework.roo.shell;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CliCommand {
/**
* @return one or more strings which must serve as the start of a particular command in order to match this method
* (these must be unique within the entire application; if not unique, behaviour is not specified)
*/
String[] value();
/**
* @return a help message for this command (the default is a blank String, which means there is no help)
*/
String help() default "";
}

View File

@@ -0,0 +1,63 @@
package org.springframework.roo.shell;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CliOption {
/**
* @return if true, the user cannot specify this option and it is provided by the shell infrastructure
* (defaults to false)
*/
boolean systemProvided() default false;
/**
* @return the name of the option, which must be unique within this {@link CliCommand} (an empty String may
* be given, which would denote this option is the default for the command)
*/
String[] key();
/**
* @return true if this option must be specified one way or the other by the user (defaults to false)
*/
boolean mandatory() default false;
/**
* @return the default value to use if this option is unspecified by the user (defaults to __NULL__, which causes null to
* be presented to any non-primitive parameter)
*/
String unspecifiedDefaultValue() default "__NULL__";
/**
* @return the default value to use if this option is included by the user, but they didn't specify an
* actual value (most commonly used for flags; defaults to __NULL__, which causes null to
* be presented to any non-primitive parameter)
*/
String specifiedDefaultValue() default "__NULL__";
/**
* Returns a string providing context-specific information (e.g. a comma-delimited
* set of keywords) to the {@link Converter} that handles the annotated parameter's type.
* <p>
* For example, if a method parameter "thing" of type "Thing" is annotated as
* follows:
* <pre>@CliOption(..., optionContext = "foo,bar", ...) Thing thing</pre>
* ... then the {@link Converter} that converts the text entered by the user
* into an instance of Thing will be passed "foo,bar" as the value of the
* <code>optionContext</code> parameter in its public methods. This allows
* the behaviour of that Converter to be individually customised for each
* {@link CliOption} of each {@link CliCommand}.
*
* @return a non-<code>null</code> string (can be empty)
*/
String optionContext() default "";
/**
* @return a help message for this option (the default is a blank String, which means there is no help)
*/
String help() default "";
}

View File

@@ -0,0 +1,40 @@
package org.springframework.roo.shell;
/**
* Utility methods relating to shell option contexts
*/
public final class CliOptionContext {
// Class fields
private static ThreadLocal<String> optionContextHolder = new ThreadLocal<String>();
/**
* Returns the option context for the current thread.
*
* @return <code>null</code> if none has been set
*/
public static String getOptionContext() {
return optionContextHolder.get();
}
/**
* Stores the given option context for the current thread.
*
* @param optionContext the option context to store
*/
public static void setOptionContext(final String optionContext) {
optionContextHolder.set(optionContext);
}
/**
* Resets the option context for the current thread.
*/
public static void resetOptionContext() {
optionContextHolder.remove();
}
/**
* Constructor is private to prevent instantiation
*/
private CliOptionContext() {}
}

View File

@@ -0,0 +1,27 @@
package org.springframework.roo.shell;
/**
* Utility methods relating to shell simple parser contexts.
*/
public final class CliSimpleParserContext {
// Class fields
private static ThreadLocal<SimpleParser> simpleParserContextHolder = new ThreadLocal<SimpleParser>();
public static Parser getSimpleParserContext() {
return simpleParserContextHolder.get();
}
public static void setSimpleParserContext(final SimpleParser simpleParserContext) {
simpleParserContextHolder.set(simpleParserContext);
}
public static void resetSimpleParserContext() {
simpleParserContextHolder.remove();
}
/**
* Constructor is private to prevent instantiation
*/
private CliSimpleParserContext() {}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.roo.shell;
/**
* Marker interface indicating a provider of one or more shell commands.
*/
public interface CommandMarker {}

View File

@@ -0,0 +1,92 @@
package org.springframework.roo.shell;
import org.springframework.roo.support.util.AnsiEscapeCode;
import org.springframework.roo.support.util.StringUtils;
public class Completion {
// Fields
private final int order;
private final String formattedValue;
private final String heading;
private final String value;
/**
* Constructor
*
* @param value
*/
public Completion(final String value) {
this(value, value, null, 0);
}
/**
* Constructor
*
* @param value
* @param formattedValue
* @param heading
* @param order
*/
public Completion(final String value, final String formattedValue, String heading, final int order) {
this.formattedValue = formattedValue;
this.order = order;
this.value = value;
if (StringUtils.hasText(heading)) {
heading = AnsiEscapeCode.decorate(heading, AnsiEscapeCode.UNDERSCORE, AnsiEscapeCode.FG_GREEN);
}
this.heading = heading;
}
public String getValue() {
return value;
}
public String getFormattedValue() {
return formattedValue;
}
public String getHeading() {
return heading;
}
public int getOrder() {
return order;
}
@Override
public String toString() {
return order + ". " + heading + " - " + value;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Completion that = (Completion) o;
if (formattedValue != null ? !formattedValue.equals(that.formattedValue) : that.formattedValue != null) {
return false;
}
if (heading != null ? !heading.equals(that.heading) : that.heading != null) {
return false;
}
if (value != null ? !value.equals(that.value) : that.value != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = value != null ? value.hashCode() : 0;
result = 31 * result + (formattedValue != null ? formattedValue.hashCode() : 0);
result = 31 * result + (heading != null ? heading.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,58 @@
package org.springframework.roo.shell;
import java.util.List;
/**
* Converts between Strings (as displayed by and entered via the shell) and Java objects
*
* @author Ben Alex
* @param <T> the type being converted to/from
*/
public interface Converter<T> {
/**
* Indicates whether this converter supports the given type in the given option context
*
* @param type the type being checked
* @param optionContext a non-<code>null</code> string that customises the
* behaviour of this converter for a given {@link CliOption} of a given
* {@link CliCommand}; the contents will have special meaning to this
* converter (e.g. be a comma-separated list of keywords known to this
* converter)
* @return see above
*/
boolean supports(Class<?> type, String optionContext);
/**
* Converts from the given String value to type T
*
* @param value the value to convert
* @param targetType the type being converted to; can't be <code>null</code>
* @param optionContext a non-<code>null</code> string that customises the
* behaviour of this converter for a given {@link CliOption} of a given
* {@link CliCommand}; the contents will have special meaning to this
* converter (e.g. be a comma-separated list of keywords known to this
* converter)
* @return see above
* @throws RuntimeException if the given value could not be converted
*/
T convertFromText(String value, Class<?> targetType, String optionContext);
/**
* Populates the given list with the possible completions
*
* @param completions the list to populate; can't be <code>null</code>
* @param targetType the type of parameter for which a string is being entered
* @param existingData what the user has typed so far
* @param optionContext a non-<code>null</code> string that customises the
* behaviour of this converter for a given {@link CliOption} of a given
* {@link CliCommand}; the contents will have special meaning to this
* converter (e.g. be a comma-separated list of keywords known to this
* converter)
* @param target
* @return <code>true</code> if all the added completions are complete
* values, or <code>false</code> if the user can press TAB to add further
* information to some or all of them
*/
boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData, String optionContext, MethodTarget target);
}

View File

@@ -0,0 +1,39 @@
package org.springframework.roo.shell;
/**
* Strategy interface to permit the controlled execution of methods.
*
* <p>
* This interface is used to enable a {@link Shell} to execute methods in a consistent, system-wide
* manner. A typical use case is to ensure user interface commands are not executed concurrently
* when other background threads are performing certain operations.
*
* @author Ben Alex
* @since 1.0
*
*/
public interface ExecutionStrategy {
/**
* Executes the method indicated by the {@link ParseResult}.
*
* @param parseResult that should be executed (never presented as null)
* @return an object which will be rendered by the {@link Shell} implementation (may return null)
* @throws RuntimeException which is handled by the {@link Shell} implementation
*/
Object execute(ParseResult parseResult) throws RuntimeException;
/**
* Indicates commands are able to be presented. This generally means all important
* system startup activities have completed.
*
* @return whether commands can be presented for processing at this time
*/
boolean isReadyForCommands();
/**
* Indicates the execution runtime should be terminated. This allows it to cleanup before returning
* control flow to the caller. Necessary for clean shutdowns.
*/
void terminate();
}

View File

@@ -0,0 +1,29 @@
package org.springframework.roo.shell;
/**
* An immutable representation of a request to exit the shell.
*
* <p>
* Implementations of the shell are free to handle these requests in whatever
* way they wish. Callers should not expect an exit request to be completed.
*
* @author Ben Alex
*/
public class ExitShellRequest {
// Constants
public static final ExitShellRequest NORMAL_EXIT = new ExitShellRequest(0);
public static final ExitShellRequest FATAL_EXIT = new ExitShellRequest(1);
public static final ExitShellRequest JVM_TERMINATED_EXIT = new ExitShellRequest(99); // Ensure 99 is maintained in o.s.r.bootstrap.Main as it's the default for a null roo.exit code
// Fields
private final int exitCode;
private ExitShellRequest(final int exitCode) {
this.exitCode = exitCode;
}
public int getExitCode() {
return exitCode;
}
}

View File

@@ -0,0 +1,110 @@
package org.springframework.roo.shell;
import java.lang.reflect.Method;
import org.springframework.roo.support.style.ToStringCreator;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.ObjectUtils;
import org.springframework.roo.support.util.StringUtils;
/**
* A method that can be executed via a shell command.
* <p>
* Immutable since 1.2.0.
*
* @author Ben Alex
*/
public class MethodTarget {
// Fields
private final Method method;
private final Object target;
private final String remainingBuffer;
private final String key;
/**
* Constructor for a <code>null remainingBuffer</code> and <code>key</code>
*
* @param method the method to invoke (required)
* @param target the object on which the method is to be invoked (required)
* @since 1.2.0
*/
public MethodTarget(final Method method, final Object target) {
this(method, target, null, null);
}
/**
* Constructor that allows all fields to be set
*
* @param method the method to invoke (required)
* @param target the object on which the method is to be invoked (required)
* @param remainingBuffer can be blank
* @param key can be blank
* @since 1.2.0
*/
public MethodTarget(final Method method, final Object target, final String remainingBuffer, final String key) {
Assert.notNull(method, "Method is required");
Assert.notNull(target, "Target is required");
this.key = StringUtils.trimToEmpty(key);
this.method = method;
this.remainingBuffer = StringUtils.trimToEmpty(remainingBuffer);
this.target = target;
}
@Override
public boolean equals(final Object other) {
if (other == this) {
return true;
}
if (!(other instanceof MethodTarget)) {
return false;
}
final MethodTarget otherMethodTarget = (MethodTarget) other;
return this.method.equals(otherMethodTarget.getMethod()) && this.target.equals(otherMethodTarget.getTarget());
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(method, target);
}
@Override
public final String toString() {
final ToStringCreator tsc = new ToStringCreator(this);
tsc.append("target", target);
tsc.append("method", method);
tsc.append("remainingBuffer", remainingBuffer);
tsc.append("key", key);
return tsc.toString();
}
/**
* @since 1.2.0
*/
public String getKey() {
return this.key;
}
/**
* @return a non-<code>null</code> method
* @since 1.2.0
*/
public Method getMethod() {
return this.method;
}
/**
* @since 1.2.0
*/
public String getRemainingBuffer() {
return this.remainingBuffer;
}
/**
* @return a non-<code>null</code> Object
* @since 1.2.0
*/
public Object getTarget() {
return this.target;
}
}

View File

@@ -0,0 +1,178 @@
package org.springframework.roo.shell;
import java.util.Comparator;
/**
* NaturalOrderComparator.java -- Perform natural order comparisons of strings in Java.
* Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
* Based on the C version by Martin Pool, of which this is more or less a straight conversion.
* Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>
*
* This software is provided as-is, without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgement in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
public class NaturalOrderComparator<E> implements Comparator<E> {
/**
* Returns the character at the given position of the given string;
* equivalent to {@link String#charAt(int)}, but handles overly large
* indices.
*
* @param s the string to read (can't be <code>null</code>)
* @param i the index at which to read (zero-based)
* @return 0 if the given index is beyond the end of the string
*/
static char charAt(final String s, final int i) {
if (i >= s.length()) {
return 0;
}
return s.charAt(i);
}
/**
* Indicates whether the given character is whitespace
*
* @param c the character to check
* @return see above
*/
public static boolean isSpace(final char c) {
switch (c) {
case ' ':
return true;
case '\n':
return true;
case '\t':
return true;
case '\f':
return true;
case '\r':
return true;
default:
return false;
}
}
int compareRight(final String a, final String b) {
int bias = 0;
int ia = 0;
int ib = 0;
// The longest run of digits wins. That aside, the greatest
// value wins, but we can't know that it will until we've scanned
// both numbers to know that they have the same magnitude, so we
// remember it in BIAS.
for (; ; ia++, ib++) {
char ca = charAt(a, ia);
char cb = charAt(b, ib);
if (!Character.isDigit(ca) && !Character.isDigit(cb)) {
return bias;
} else if (!Character.isDigit(ca)) {
return -1;
} else if (!Character.isDigit(cb)) {
return +1;
} else if (ca < cb) {
if (bias == 0) {
bias = -1;
}
} else if (ca > cb) {
if (bias == 0)
bias = +1;
} else if (ca == 0 && cb == 0) {
return bias;
}
}
}
protected String stringify(final E object) {
return object.toString();
}
public int compare(final E o1, final E o2) {
if (o1 == null && o2 == null) {
return 1;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
String a = stringify(o1);
String b = stringify(o2);
int ia = 0, ib = 0;
int nza = 0, nzb = 0;
char ca, cb;
int result;
while (true) {
// Only count the number of zeroes leading the last number compared
nza = nzb = 0;
ca = charAt(a, ia);
cb = charAt(b, ib);
// Skip over leading spaces or zeros
while (isSpace(ca) || ca == '0') {
if (ca == '0') {
nza++;
} else {
// Only count consecutive zeroes
nza = 0;
}
ca = charAt(a, ++ia);
}
while (isSpace(cb) || cb == '0') {
if (cb == '0') {
nzb++;
} else {
// Only count consecutive zeroes
nzb = 0;
}
cb = charAt(b, ++ib);
}
// Process run of digits
if (Character.isDigit(ca) && Character.isDigit(cb)) {
if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
return result;
}
}
if (ca == 0 && cb == 0) {
// The strings compare the same. Perhaps the caller
// will want to call strcmp to break the tie.
return nza - nzb;
}
if (ca < cb) {
return -1;
} else if (ca > cb) {
return +1;
}
++ia;
++ib;
}
}
}

View File

@@ -0,0 +1,92 @@
package org.springframework.roo.shell;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.roo.support.style.ToStringCreator;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.StringUtils;
/**
* Immutable representation of the outcome of parsing a given shell line.
*
* <p>
* Note that contained objects (the instance and the arguments) may be mutable, as the shell infrastructure
* has no way of restricting which methods can be the target of CLI commands and nor the arguments
* they will accept via the {@link Converter} infrastructure.
*
* @author Ben Alex
* @since 1.0
*/
public class ParseResult {
// Fields
private final Method method;
private final Object instance;
private final Object[] arguments; // May be null if no arguments needed
public ParseResult(final Method method, final Object instance, final Object[] arguments) {
Assert.notNull(method, "Method required");
Assert.notNull(instance, "Instance required");
int length = arguments == null ? 0 : arguments.length;
Assert.isTrue(method.getParameterTypes().length == length, "Required " + method.getParameterTypes().length + " arguments, but received " + length);
this.method = method;
this.instance = instance;
this.arguments = arguments;
}
public Method getMethod() {
return method;
}
public Object getInstance() {
return instance;
}
public Object[] getArguments() {
return arguments;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(arguments);
result = prime * result + ((instance == null) ? 0 : instance.hashCode());
result = prime * result + ((method == null) ? 0 : method.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ParseResult other = (ParseResult) obj;
if (!Arrays.equals(arguments, other.arguments))
return false;
if (instance == null) {
if (other.instance != null)
return false;
} else if (!instance.equals(other.instance))
return false;
if (method == null) {
if (other.method != null)
return false;
} else if (!method.equals(other.method))
return false;
return true;
}
@Override
public String toString() {
ToStringCreator tsc = new ToStringCreator(this);
tsc.append("method", method);
tsc.append("instance", instance);
tsc.append("arguments", StringUtils.arrayToCommaDelimitedString(arguments));
return tsc.toString();
}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.roo.shell;
import java.util.List;
/**
* Interface for {@link SimpleParser}.
*
* @author Ben Alex
* @author Alan Stewart
* @since 1.0
*/
public interface Parser {
ParseResult parse(String buffer);
/**
* Populates a list of completion candidates. This method is required for backward compatibility for STS versions up to 2.8.0.
*
* @param buffer
* @param cursor
* @param candidates
* @return
*/
int complete(String buffer, int cursor, List<String> candidates);
/**
* Populates a list of completion candidates.
*
* @param buffer
* @param cursor
* @param candidates
* @return
*/
int completeAdvanced(String buffer, int cursor, List<Completion> candidates);
}

View File

@@ -0,0 +1,181 @@
package org.springframework.roo.shell;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.roo.support.util.Assert;
/**
* Utilities for parsing.
*
* @author Ben Alex
* @since 1.0
*/
public class ParserUtils {
private ParserUtils() {}
/**
* Converts a particular buffer into a tokenized structure.
*
* <p>
* Properly treats double quotes (") as option delimiters.
*
* <p>
* Expects option names to be preceded by a single or double dash. We call this an "option marker".
*
* <p>
* Treats spaces as the default option tokenizer.
*
* <p>
* Any token without an option marker is considered the default. The default is returned in the Map as an element with an empty string key (""). There can only be a single default.
*
* @param remainingBuffer to tokenize
* @return a Map where keys are the option names (minus any dashes) and values are the option values (any double-quotes are removed)
*/
public static Map<String, String> tokenize(final String remainingBuffer) {
Assert.notNull(remainingBuffer, "Remaining buffer cannot be null, although it can be empty");
Map<String, String> result = new LinkedHashMap<String, String>();
StringBuilder currentOption = new StringBuilder();
StringBuilder currentValue = new StringBuilder();
boolean inQuotes = false;
// Verify correct number of double quotes are present
int count = 0;
for (char c : remainingBuffer.toCharArray()) {
if ('"' == c) {
count++;
}
}
Assert.isTrue(count % 2 == 0, "Cannot have an unbalanced number of quotation marks");
if ("".equals(remainingBuffer.trim())) {
// They've not specified anything, so exit now
return result;
}
String[] split = remainingBuffer.split(" ");
for (int i = 0; i < split.length; i++) {
String currentToken = split[i];
if (currentToken.startsWith("\"") && currentToken.endsWith("\"") && currentToken.length() > 1) {
String tokenLessDelimiters = currentToken.substring(1, currentToken.length() - 1);
currentValue.append(tokenLessDelimiters);
// Store this token
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
continue;
}
if (inQuotes) {
// We're only interested in this token series ending
if (currentToken.endsWith("\"")) {
String tokenLessDelimiters = currentToken.substring(0, currentToken.length() - 1);
currentValue.append(" ").append(tokenLessDelimiters);
inQuotes = false;
// Store this now-ended token series
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
} else {
// The current token series has not ended
currentValue.append(" ").append(currentToken);
}
continue;
}
if (currentToken.startsWith("\"")) {
// We're about to start a new delimited token
String tokenLessDelimiters = currentToken.substring(1);
currentValue.append(tokenLessDelimiters);
inQuotes = true;
continue;
}
if (currentToken.trim().equals("")) {
// It's simply empty, so ignore it (ROO-23)
continue;
}
if (currentToken.startsWith("--")) {
// We're about to start a new option marker
// First strip all of the - or -- or however many there are
int lastIndex = currentToken.lastIndexOf("-");
String tokenLessDelimiters = currentToken.substring(lastIndex + 1);
currentOption.append(tokenLessDelimiters);
// Store this token if it's the last one, or the next token starts with a "-"
if (i + 1 == split.length) {
// We're at the end of the tokens, so store this one and stop processing
store(result, currentOption, currentValue);
break;
}
if (split[i + 1].startsWith("-")) {
// A new token is being started next iteration, so store this one now
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
continue;
}
// We must be in a standard token
// If the standard token has no option name, we allow it to contain unquoted spaces
if (currentOption.length() == 0) {
if (currentValue.length() > 0) {
// Existing content, so add a space first
currentValue.append(" ");
}
currentValue.append(currentToken);
// Store this token if it's the last one, or the next token starts with a "-"
if (i + 1 == split.length) {
// We're at the end of the tokens, so store this one and stop processing
store(result, currentOption, currentValue);
break;
}
if (split[i + 1].startsWith("--")) {
// A new token is being started next iteration, so store this one now
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
continue;
}
// This is an ordinary token, so store it now
currentValue.append(currentToken);
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
// Strip out an empty default option, if it was returned (ROO-379)
if (result.containsKey("") && result.get("").trim().equals("")) {
result.remove("");
}
return result;
}
private static void store(final Map<String, String> results, final StringBuilder currentOption, final StringBuilder currentValue) {
if (currentOption.length() > 0) {
// There is an option marker
String option = currentOption.toString();
Assert.isTrue(!results.containsKey(option), "You cannot specify option '" + option + "' more than once in a single command");
results.put(option, currentValue.toString());
} else {
// There was no option marker, so verify this isn't the first
Assert.isTrue(!results.containsKey(""), "You cannot add more than one default option ('" + currentValue.toString() + "') in a single command");
results.put("", currentValue.toString());
}
}
}

View File

@@ -0,0 +1,98 @@
package org.springframework.roo.shell;
import java.io.File;
import java.util.logging.Level;
import org.springframework.roo.shell.event.ShellStatusProvider;
/**
* Specifies the contract for an interactive shell.
*
* <p>
* Any interactive shell class which implements these methods can be launched by the roo-bootstrap mechanism.
*
* <p>
* It is envisaged implementations will be provided for JLine initially, with possible implementations for
* Eclipse in the future.
*
* @author Ben Alex
* @since 1.0
*/
public interface Shell extends ShellStatusProvider, ShellPromptAccessor {
/**
* The slot name to use with {@link #flash(Level, String, String)} if a caller wishes to modify the window title.
* This may not be supported by all operating system shells. It is provided on a best-effort basis only.
*/
String WINDOW_TITLE_SLOT = "WINDOW_TITLE_SLOT";
/**
* Presents a console prompt and allows the user to interact with the shell. The shell should not return
* to the caller until the user has finished their session (by way of a "quit" or similar command).
*/
void promptLoop();
/**
* @return null if no exit was requested, otherwise the last exit code indicated to the shell to use
*/
ExitShellRequest getExitShellRequest();
/**
* Runs the specified command. Control will return to the caller after the command is run.
*
* @param line to execute (required)
* @return true if the command was successful, false if there was an exception
*/
boolean executeCommand(String line);
/**
* Indicates the shell should switch into a lower-level development mode. The exact meaning varies by
* shell implementation.
*
* @param developmentMode true if development mode should be enabled, false otherwise
*/
void setDevelopmentMode(boolean developmentMode);
/**
* Displays a progress notification to the user. This notification will ideally be displayed in a
* consistent screen location by the shell implementation.
*
* <p>
* An implementation may allow multiple messages to be displayed concurrently. So an implementation can
* determine when a flash message replaces a previous flash message, callers should allocate a unique
* "slot" name for their messages. It is suggested the class name of the caller be used. This way a
* slot will be updated without conflicting with flash message sequences from other slots.
*
* <p>
* Passing an empty string in as the "message" indicates the slot should be cleared.
*
* <p>
* An implementation need not necessarily use the level or slot concepts. They are expected to be
* used in most cases, though.
*
* @param level the importance of the message (cannot be null)
* @param message to display (cannot be null, but may be empty)
* @param slot the identification slot for the message (cannot be null or empty)
*/
void flash(Level level, String message, String slot);
boolean isDevelopmentMode();
/**
* Changes the "path" displayed in the shell prompt. An implementation will ensure this path is
* included on the screen, taking care to merge it with the product name and handle any special
* formatting requirements (such as ANSI, if supported by the implementation).
*
* @param path to set (can be null or empty; must NOT be formatted in any special way eg ANSI codes)
*/
void setPromptPath(String path);
void setPromptPath(String path, boolean overrideStyle);
/**
* Returns the home directory of the current running shell instance
*
* @return the home directory of the current shell instance
*/
File getHome();
}

View File

@@ -0,0 +1,16 @@
package org.springframework.roo.shell;
/**
* Obtains the prompt used by a {@link Shell}.
*
* @author Ben Alex
* @since 1.0
*/
public interface ShellPromptAccessor {
/**
* @return the shell prompt (never null; the result may include special characters such as ANSI
* escape codes if the implementation is using them)
*/
String getShellPrompt();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
import org.springframework.roo.shell.SimpleParser;
/**
* Available commands converter.
*
* @author Ben Alex
* @since 1.0
*/
public class AvailableCommandsConverter implements Converter<String> {
public String convertFromText(final String text, final Class<?> requiredType, final String optionContext) {
return text;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return String.class.isAssignableFrom(requiredType) && "availableCommands".equals(optionContext);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
if (target.getTarget() instanceof SimpleParser) {
SimpleParser cmd = (SimpleParser) target.getTarget();
// Only include the first word of each command
for (String s : cmd.getEveryCommand()) {
if (s.contains(" ")) {
completions.add(new Completion(s.substring(0, s.indexOf(" "))));
} else {
completions.add(new Completion(s));
}
}
}
return true;
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.roo.shell.converters;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link BigDecimal}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class BigDecimalConverter implements Converter<BigDecimal> {
public BigDecimal convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new BigDecimal(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return BigDecimal.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.roo.shell.converters;
import java.math.BigInteger;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link BigInteger}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class BigIntegerConverter implements Converter<BigInteger> {
public BigInteger convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new BigInteger(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return BigInteger.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,40 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Boolean}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class BooleanConverter implements Converter<Boolean> {
public Boolean convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
if ("true".equalsIgnoreCase(value) || "1".equals(value) || "yes".equalsIgnoreCase(value)) {
return true;
} else if ("false".equalsIgnoreCase(value) || "0".equals(value) || "no".equalsIgnoreCase(value)) {
return false;
} else {
throw new IllegalArgumentException("Cannot convert " + value + " to type Boolean.");
}
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
completions.add(new Completion("true"));
completions.add(new Completion("false"));
completions.add(new Completion("yes"));
completions.add(new Completion("no"));
completions.add(new Completion("1"));
completions.add(new Completion("0"));
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Boolean.class.isAssignableFrom(requiredType) || boolean.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Character}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class CharacterConverter implements Converter<Character> {
public Character convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return value.charAt(0);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Character.class.isAssignableFrom(requiredType) || char.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,47 @@
package org.springframework.roo.shell.converters;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Date}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class DateConverter implements Converter<Date> {
// Fields
private final DateFormat dateFormat;
public DateConverter() {
this.dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.getDefault());
}
public DateConverter(final DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
public Date convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
try {
return dateFormat.parse(value);
} catch (ParseException e) {
throw new IllegalArgumentException("Could not parse date: " + e.getMessage());
}
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Date.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Double}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class DoubleConverter implements Converter<Double> {
public Double convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new Double(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Double.class.isAssignableFrom(requiredType) || double.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,38 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Enum}.
*
* @author Ben Alex
* @author Alan Stewart
* @since 1.0
*/
@SuppressWarnings("all")
public class EnumConverter implements Converter<Enum> {
public Enum convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
Class<Enum> enumClass = (Class<Enum>) requiredType;
return Enum.valueOf(enumClass, value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
Class<Enum> enumClass = (Class<Enum>) requiredType;
for (Enum enumValue : enumClass.getEnumConstants()) {
String candidate = enumValue.name();
if ("".equals(existingData) || candidate.startsWith(existingData) || existingData.startsWith(candidate) || candidate.toUpperCase().startsWith(existingData.toUpperCase()) || existingData.toUpperCase().startsWith(candidate.toUpperCase())) {
completions.add(new Completion(candidate));
}
}
return true;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Enum.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,127 @@
package org.springframework.roo.shell.converters;
import java.io.File;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.FileUtils;
/**
* {@link Converter} for {@link File}.
*
* @author Stefan Schmidt
* @author Roman Kuzmik
* @author Ben Alex
* @since 1.0
*/
public abstract class FileConverter implements Converter<File> {
private static final String HOME_DIRECTORY_SYMBOL = "~";
// Constants
private static final String home = System.getProperty("user.home");
// Fields
/**
* @return the "current working directory" this {@link FileConverter} should use if the user fails to provide
* an explicit directory in their input (required)
*/
protected abstract File getWorkingDirectory();
public File convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new File(convertUserInputIntoAFullyQualifiedPath(value));
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String originalUserInput, final String optionContext, final MethodTarget target) {
String adjustedUserInput = convertUserInputIntoAFullyQualifiedPath(originalUserInput);
String directoryData = adjustedUserInput.substring(0, adjustedUserInput.lastIndexOf(File.separator) + 1);
adjustedUserInput = adjustedUserInput.substring(adjustedUserInput.lastIndexOf(File.separator) + 1);
populate(completions, adjustedUserInput, originalUserInput, directoryData);
return false;
}
protected void populate(final List<Completion> completions, final String adjustedUserInput, final String originalUserInput, final String directoryData) {
File directory = new File(directoryData);
if (!directory.isDirectory()) {
return;
}
for (File file : directory.listFiles()) {
if (adjustedUserInput == null || adjustedUserInput.length() == 0 ||
file.getName().toLowerCase().startsWith(adjustedUserInput.toLowerCase())) {
String completion = "";
if (directoryData.length() > 0)
completion += directoryData;
completion += file.getName();
completion = convertCompletionBackIntoUserInputStyle(originalUserInput, completion);
if (file.isDirectory()) {
completions.add(new Completion(completion + File.separator));
} else {
completions.add(new Completion(completion));
}
}
}
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return File.class.isAssignableFrom(requiredType);
}
private String convertCompletionBackIntoUserInputStyle(final String originalUserInput, final String completion) {
if (FileUtils.denotesAbsolutePath(originalUserInput)) {
// Input was originally as a fully-qualified path, so we just keep the completion in that form
return completion;
}
if (originalUserInput.startsWith(HOME_DIRECTORY_SYMBOL)) {
// Input originally started with this symbol, so replace the user's home directory with it again
Assert.notNull(home, "Home directory could not be determined from system properties");
return HOME_DIRECTORY_SYMBOL + completion.substring(home.length());
}
// The path was working directory specific, so strip the working directory given the user never typed it
return completion.substring(getWorkingDirectoryAsString().length());
}
/**
* If the user input starts with a tilde character (~), replace the tilde character with the
* user's home directory. If the user input does not start with a tilde, simply return the original
* user input without any changes if the input specifies an absolute path, or return an absolute path
* based on the working directory if the input specifies a relative path.
*
* @param userInput the user input, which may commence with a tilde (required)
* @return a string that is guaranteed to no longer contain a tilde as the first character (never null)
*/
private String convertUserInputIntoAFullyQualifiedPath(final String userInput) {
if (FileUtils.denotesAbsolutePath(userInput)) {
// Input is already in a fully-qualified path form
return userInput;
}
if (userInput.startsWith(HOME_DIRECTORY_SYMBOL)) {
// Replace this symbol with the user's actual home directory
Assert.notNull(home, "Home directory could not be determined from system properties");
if (userInput.length() > 1) {
return home + userInput.substring(1);
}
}
// The path is working directory specific, so prepend the working directory
String fullPath = getWorkingDirectoryAsString() + userInput;
return fullPath;
}
private String getWorkingDirectoryAsString() {
try {
return getWorkingDirectory().getCanonicalPath() + File.separator;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Float}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class FloatConverter implements Converter<Float> {
public Float convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new Float(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Float.class.isAssignableFrom(requiredType) || float.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Integer}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class IntegerConverter implements Converter<Integer> {
public Integer convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new Integer(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Integer.class.isAssignableFrom(requiredType) || int.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,39 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import java.util.Locale;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Locale}. Supports locales
* with ISO-639 (ie 'en') or a combination of ISO-639 and
* ISO-3166 (ie 'en_AU').
*
* @author Stefan Schmidt
* @since 1.1
*/
public class LocaleConverter implements Converter<Locale> {
public Locale convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
if (value.length() == 2) {
// In case only a simpele ISO-639 code is provided we use that code also for the country (ie 'de_DE')
return new Locale(value, value.toUpperCase());
} else if (value.length() == 5) {
String[] split = value.split("_");
return new Locale(split[0], split[1]);
} else {
return null;
}
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Locale.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Long}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class LongConverter implements Converter<Long> {
public Long convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new Long(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Long.class.isAssignableFrom(requiredType) || long.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link Short}.
*
* @author Stefan Schmidt
* @since 1.0
*/
public class ShortConverter implements Converter<Short> {
public Short convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return new Short(value);
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return Short.class.isAssignableFrom(requiredType) || short.class.isAssignableFrom(requiredType);
}
}

View File

@@ -0,0 +1,17 @@
package org.springframework.roo.shell.converters;
import org.springframework.roo.shell.Converter;
/**
* Interface for adding and removing classes that provide static fields which should
* be made available via a {@link Converter}.
*
* @author Ben Alex
* @since 1.0
*/
public interface StaticFieldConverter extends Converter<Object> {
void add(Class<?> clazz);
void remove(Class<?> clazz);
}

View File

@@ -0,0 +1,90 @@
package org.springframework.roo.shell.converters;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.StringUtils;
/**
* A simple {@link Converter} for those classes which provide public static fields to represent possible
* textual values.
*
* @author Stefan Schmidt
* @author Ben Alex
* @since 1.0
*/
public class StaticFieldConverterImpl implements StaticFieldConverter {
// Fields
private final Map<Class<?>,Map<String,Field>> fields = new HashMap<Class<?>,Map<String,Field>>();
public void add(final Class<?> clazz) {
Assert.notNull(clazz, "A class to provide conversion services is required");
Assert.isNull(fields.get(clazz), "Class '" + clazz + "' is already registered for completion services");
Map<String,Field> ffields = new HashMap<String, Field>();
for (Field field : clazz.getFields()) {
int modifier = field.getModifiers();
if (Modifier.isStatic(modifier) && Modifier.isPublic(modifier)) {
ffields.put(field.getName(), field);
}
}
Assert.notEmpty(ffields, "Zero public static fields accessible in '" + clazz + "'");
fields.put(clazz, ffields);
}
public void remove(final Class<?> clazz) {
Assert.notNull(clazz, "A class that was providing conversion services is required");
fields.remove(clazz);
}
public Object convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
if (StringUtils.isBlank(value)) {
return null;
}
Map<String,Field> ffields = fields.get(requiredType);
if (ffields == null) {
return null;
}
Field f = ffields.get(value);
if (f == null) {
// Fallback to case insensitive search
for (Field candidate : ffields.values()) {
if (candidate.getName().equalsIgnoreCase(value)) {
f = candidate;
break;
}
}
if (f == null) {
// Still not found, despite a case-insensitive search
return null;
}
}
try {
return f.get(null);
} catch (Exception ex) {
throw new IllegalStateException("Unable to acquire field '" + value + "' from '" + requiredType.getName() + "'", ex);
}
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
Map<String,Field> ffields = fields.get(requiredType);
if (ffields == null) {
return true;
}
for (String field : ffields.keySet()) {
completions.add(new Completion(field));
}
return true;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return fields.get(requiredType) != null;
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.roo.shell.converters;
import java.util.List;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* {@link Converter} for {@link String}.
*
* @author Ben Alex
* @since 1.0
*/
public class StringConverter implements Converter<String> {
public String convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
return value;
}
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
return false;
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return String.class.isAssignableFrom(requiredType) && (optionContext == null || !optionContext.contains("disable-string-converter"));
}
}

View File

@@ -0,0 +1,67 @@
package org.springframework.roo.shell.event;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.roo.shell.ParseResult;
import org.springframework.roo.shell.event.ShellStatus.Status;
import org.springframework.roo.support.util.Assert;
/**
* Provides a convenience superclass for those shells wishing to publish status messages.
*
* @author Ben Alex
* @since 1.0
*/
public abstract class AbstractShellStatusPublisher implements ShellStatusProvider {
// Fields
protected Set<ShellStatusListener> shellStatusListeners = new CopyOnWriteArraySet<ShellStatusListener>();
protected ShellStatus shellStatus = new ShellStatus(Status.STARTING);
public final void addShellStatusListener(final ShellStatusListener shellStatusListener) {
Assert.notNull(shellStatusListener, "Status listener required");
synchronized (shellStatus) {
shellStatusListeners.add(shellStatusListener);
}
}
public final void removeShellStatusListener(final ShellStatusListener shellStatusListener) {
Assert.notNull(shellStatusListener, "Status listener required");
synchronized (shellStatus) {
shellStatusListeners.remove(shellStatusListener);
}
}
public final ShellStatus getShellStatus() {
synchronized (shellStatus) {
return shellStatus;
}
}
protected void setShellStatus(final Status shellStatus) {
setShellStatus(shellStatus, null, null);
}
protected void setShellStatus(final Status shellStatus, final String msg, final ParseResult parseResult) {
Assert.notNull(shellStatus, "Shell status required");
synchronized (this.shellStatus) {
ShellStatus st;
if (msg == null || msg.length() == 0) {
st = new ShellStatus(shellStatus);
} else {
st = new ShellStatus(shellStatus, msg, parseResult);
}
if (this.shellStatus.equals(st)) {
return;
}
for (ShellStatusListener listener : shellStatusListeners) {
listener.onShellStatusChange(this.shellStatus, st);
}
this.shellStatus = st;
}
}
}

View File

@@ -0,0 +1,95 @@
package org.springframework.roo.shell.event;
import org.springframework.roo.shell.ParseResult;
/**
* Represents the different states that a shell can legally be in.
*
* <p>
* There is no "shut down" state because the shell would have been terminated by
* that stage and potentially garbage collected. There is no guarantee that a
* shell implementation will necessarily publish every state.
*
* @author Ben Alex
* @author Stefan Schmidt
* @since 1.0
*/
public class ShellStatus {
// Fields
private final Status status;
private String message = "";
private ParseResult parseResult;
public enum Status {
STARTING,
STARTED,
USER_INPUT,
PARSING,
EXECUTING,
EXECUTION_RESULT_PROCESSING,
EXECUTION_SUCCESS,
EXECUTION_FAILED,
SHUTTING_DOWN
}
ShellStatus(final Status status) {
this.status = status;
}
ShellStatus(final Status status, final String msg, final ParseResult parseResult) {
this.status = status;
this.message = msg;
this.parseResult = parseResult;
}
public String getMessage() {
return message;
}
public Status getStatus() {
return status;
}
public final ParseResult getParseResult() {
return parseResult;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((message == null) ? 0 : message.hashCode());
result = prime * result
+ ((parseResult == null) ? 0 : parseResult.hashCode());
result = prime * result + ((status == null) ? 0 : status.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ShellStatus other = (ShellStatus) obj;
if (message == null) {
if (other.message != null)
return false;
} else if (!message.equals(other.message))
return false;
if (parseResult == null) {
if (other.parseResult != null)
return false;
} else if (!parseResult.equals(other.parseResult))
return false;
if (status == null) {
if (other.status != null)
return false;
} else if (!status.equals(other.status))
return false;
return true;
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.roo.shell.event;
/**
* Implemented by classes that wish to be notified of shell status changes.
*
* @author Ben Alex
* @since 1.0
*/
public interface ShellStatusListener {
/**
* Invoked by the shell to report a new status.
*
* @param oldStatus the old status
* @param newStatus the new status
*/
void onShellStatusChange(ShellStatus oldStatus, ShellStatus newStatus);
}

View File

@@ -0,0 +1,48 @@
package org.springframework.roo.shell.event;
/**
* Implemented by shells that support the publication of shell status changes.
*
* <p>
* Implementations are not required to provide any guarantees with respect to the order
* in which notifications are delivered to listeners.
*
* <p>
* Implementations must permit modification of the listener list, even while delivering
* event notifications to listeners. However, listeners do not receive any guarantee that
* their addition or removal from the listener list will be effective or not for any event
* notification that is currently proceeding.
*
* <p>
* Implementations must ensure that status notifications are only delivered when an actual
* change has taken place.
*
* @author Ben Alex
* @since 1.0
*/
public interface ShellStatusProvider {
/**
* Registers a new status listener.
*
* @param shellStatusListener to register (cannot be null)
*/
void addShellStatusListener(ShellStatusListener shellStatusListener);
/**
* Removes an existing status listener.
*
* <p>
* If the presented status listener is not found, the method returns without exception.
*
* @param shellStatusListener to remove (cannot be null)
*/
void removeShellStatusListener(ShellStatusListener shellStatusListener);
/**
* Returns the current shell status.
*
* @return the current status (never null)
*/
ShellStatus getShellStatus();
}

View File

@@ -0,0 +1,219 @@
package org.springframework.roo.support.ant;
import java.util.Map;
/**
* Package-protected helper class for {@link AntPathMatcher}. Tests whether or not a string matches against a pattern.
* The pattern may contain special characters:<br> '*' means zero or more characters<br> '?' means one and only one
* character, '{' and '}' indicate a uri template pattern
*
* @author Arjen Poutsma
* @since 3.0
*/
class AntPatchStringMatcher {
// Fields
private final char[] patArr;
private final char[] strArr;
private int patIdxStart = 0;
private int patIdxEnd;
private int strIdxStart = 0;
private int strIdxEnd;
private char ch;
private final Map<String, String> uriTemplateVariables;
/** Constructs a new instance of the <code>AntPatchStringMatcher</code>. */
AntPatchStringMatcher(final String pattern, final String str, final Map<String, String> uriTemplateVariables) {
patArr = pattern.toCharArray();
strArr = str.toCharArray();
this.uriTemplateVariables = uriTemplateVariables;
patIdxEnd = patArr.length - 1;
strIdxEnd = strArr.length - 1;
}
private void addTemplateVariable(final int curlyIdxStart, final int curlyIdxEnd, final int valIdxStart, final int valIdxEnd) {
if (uriTemplateVariables != null) {
String varName = new String(patArr, curlyIdxStart + 1, curlyIdxEnd - curlyIdxStart - 1);
String varValue = new String(strArr, valIdxStart, valIdxEnd - valIdxStart + 1);
uriTemplateVariables.put(varName, varValue);
}
}
/**
* Main entry point.
*
* @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
*/
boolean matchStrings() {
if (shortcutPossible()) {
return doShortcut();
}
if (patternContainsOnlyStar()) {
return true;
}
if (patternContainsOneTemplateVariable()) {
addTemplateVariable(0, patIdxEnd, 0, strIdxEnd);
return true;
}
if (!matchBeforeFirstStarOrCurly()) {
return false;
}
if (allCharsUsed()) {
return onlyStarsLeft();
}
if (!matchAfterLastStarOrCurly()) {
return false;
}
if (allCharsUsed()) {
return onlyStarsLeft();
}
// Process pattern between stars. padIdxStart and patIdxEnd point always to a '*'.
while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
int patIdxTmp;
if (patArr[patIdxStart] == '{') {
patIdxTmp = findClosingCurly();
addTemplateVariable(patIdxStart, patIdxTmp, strIdxStart, strIdxEnd);
patIdxStart = patIdxTmp + 1;
strIdxStart = strIdxEnd + 1;
continue;
}
patIdxTmp = findNextStarOrCurly();
if (consecutiveStars(patIdxTmp)) {
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd
int patLength = (patIdxTmp - patIdxStart - 1);
int strLength = (strIdxEnd - strIdxStart + 1);
int foundIdx = -1;
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
ch = patArr[patIdxStart + j + 1];
if (ch != '?') {
if (ch != strArr[strIdxStart + i + j]) {
continue strLoop;
}
}
}
foundIdx = strIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
patIdxStart = patIdxTmp;
strIdxStart = foundIdx + patLength;
}
return onlyStarsLeft();
}
private boolean consecutiveStars(final int patIdxTmp) {
if (patIdxTmp == patIdxStart + 1 && patArr[patIdxStart] == '*' && patArr[patIdxTmp] == '*') {
// Two stars next to each other, skip the first one.
patIdxStart++;
return true;
}
return false;
}
private int findNextStarOrCurly() {
for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
if (patArr[i] == '*' || patArr[i] == '{') {
return i;
}
}
return -1;
}
private int findClosingCurly() {
for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
if (patArr[i] == '}') {
return i;
}
}
return -1;
}
private boolean onlyStarsLeft() {
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
private boolean allCharsUsed() {
return strIdxStart > strIdxEnd;
}
private boolean shortcutPossible() {
for (char ch : patArr) {
if (ch == '*' || ch == '{' || ch == '}') {
return false;
}
}
return true;
}
private boolean doShortcut() {
if (patIdxEnd != strIdxEnd) {
return false; // Pattern and string do not have the same size
}
for (int i = 0; i <= patIdxEnd; i++) {
ch = patArr[i];
if (ch != '?') {
if (ch != strArr[i]) {
return false;// Character mismatch
}
}
}
return true; // String matches against pattern
}
private boolean patternContainsOnlyStar() {
return (patIdxEnd == 0 && patArr[0] == '*');
}
private boolean patternContainsOneTemplateVariable() {
if ((patIdxEnd >= 2 && patArr[0] == '{' && patArr[patIdxEnd] == '}')) {
for (int i = 1; i < patIdxEnd; i++) {
if (patArr[i] == '}') {
return false;
}
}
return true;
}
return false;
}
private boolean matchBeforeFirstStarOrCurly() {
while ((ch = patArr[patIdxStart]) != '*' && ch != '{' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxStart]) {
return false;
}
}
patIdxStart++;
strIdxStart++;
}
return true;
}
private boolean matchAfterLastStarOrCurly() {
while ((ch = patArr[patIdxEnd]) != '*' && ch != '}' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxEnd]) {
return false;
}
}
patIdxEnd--;
strIdxEnd--;
}
return true;
}
}

View File

@@ -0,0 +1,243 @@
package org.springframework.roo.support.ant;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.StringUtils;
/**
* PathMatcher implementation for Ant-style path patterns.
* Examples are provided below.
*
* @author Alef Arendsen
* @author Juergen Hoeller
* @author Rob Harrop
* @since 16.07.2003
*/
public class AntPathMatcher implements PathMatcher {
/** Default path separator: "/" */
public static final String DEFAULT_PATH_SEPARATOR = "/";
private String pathSeparator = DEFAULT_PATH_SEPARATOR;
/**
* Set the path separator to use for pattern parsing.
* Default is "/", as in Ant.
*/
public void setPathSeparator(final String pathSeparator) {
this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
}
public boolean isPattern(final String path) {
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
public boolean match(final String pattern, final String path) {
return doMatch(pattern, path, true, null);
}
public boolean matchStart(final String pattern, final String path) {
return doMatch(pattern, path, false, null);
}
/**
* Actually match the given <code>path</code> against the given <code>pattern</code>.
* @param pattern the pattern to match against
* @param path the path String to test
* @param fullMatch whether a full pattern match is required
* (else a pattern match as far as the given base path goes is sufficient)
* @return <code>true</code> if the supplied <code>path</code> matched,
* <code>false</code> if it didn't
*/
protected boolean doMatch(final String pattern, final String path, final boolean fullMatch, final Map<String, String> uriTemplateVariables) {
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxStart];
if ("**".equals(patDir)) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
} else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
} else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
}
// Up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxEnd];
if (patDir.equals("**")) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' situation, so skip one
pattIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd
int patLength = (patIdxTmp - pattIdxStart - 1);
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1;
strLoop: for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = pattDirs[pattIdxStart + j + 1];
String subStr = pathDirs[pathIdxStart + i + j];
if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
/**
* Tests whether or not a string matches against a pattern.
* The pattern may contain two special characters:<br>
* '*' means zero or more characters<br>
* '?' means one and only one character
* @param pattern pattern to match against.
* Must not be <code>null</code>.
* @param str string which must be matched against the pattern.
* Must not be <code>null</code>.
* @return <code>true</code> if the string matches against the
* pattern, or <code>false</code> otherwise.
*/
private boolean matchStrings(final String pattern, final String str, final Map<String, String> uriTemplateVariables) {
AntPatchStringMatcher matcher = new AntPatchStringMatcher(pattern, str, uriTemplateVariables);
return matcher.matchStrings();
}
/**
* Given a pattern and a full path, determine the pattern-mapped part.
* <p>For example:
* <ul>
* <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
* <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
* <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
* <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
* <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
* <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
* <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
* <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
* </ul>
* <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
* and '<code>path</code>', but does <strong>not</strong> enforce this.
*/
public String extractPathWithinPattern(final String pattern, final String path) {
String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
StringBuilder builder = new StringBuilder();
// Add any path parts that have a wildcarded pattern part.
int puts = 0;
for (int i = 0; i < patternParts.length; i++) {
String patternPart = patternParts[i];
if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
builder.append(this.pathSeparator);
}
builder.append(pathParts[i]);
puts++;
}
}
// Append any trailing path parts.
for (int i = patternParts.length; i < pathParts.length; i++) {
if (puts > 0 || i > 0) {
builder.append(this.pathSeparator);
}
builder.append(pathParts[i]);
}
return builder.toString();
}
public Map<String, String> extractUriTemplateVariables(final String pattern, final String path) {
Map<String, String> variables = new LinkedHashMap<String, String>();
boolean result = doMatch(pattern, path, true, variables);
Assert.state(result, "Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
return variables;
}
}

View File

@@ -0,0 +1,84 @@
package org.springframework.roo.support.ant;
import java.util.Map;
/**
* Strategy interface for <code>String</code>-based path matching.
*
* <p>The default implementation is {@link AntPathMatcher}, supporting the
* Ant-style pattern syntax.
*
* @author Juergen Hoeller
* @since 1.2.0
* @see AntPathMatcher
*/
public interface PathMatcher {
/**
* Does the given <code>path</code> represent a pattern that can be matched
* by an implementation of this interface?
* <p>If the return value is <code>false</code>, then the {@link #match}
* method does not have to be used because direct equality comparisons
* on the static path Strings will lead to the same result.
* @param path the path String to check
* @return <code>true</code> if the given <code>path</code> represents a pattern
*/
boolean isPattern(String path);
/**
* Match the given <code>path</code> against the given <code>pattern</code>,
* according to this PathMatcher's matching strategy.
* @param pattern the pattern to match against
* @param path the path String to test
* @return <code>true</code> if the supplied <code>path</code> matched,
* <code>false</code> if it didn't
*/
boolean match(String pattern, String path);
/**
* Match the given <code>path</code> against the corresponding part of the given
* <code>pattern</code>, according to this PathMatcher's matching strategy.
* <p>Determines whether the pattern at least matches as far as the given base
* path goes, assuming that a full path may then match as well.
* @param pattern the pattern to match against
* @param path the path String to test
* @return <code>true</code> if the supplied <code>path</code> matched,
* <code>false</code> if it didn't
*/
boolean matchStart(String pattern, String path);
/**
* Given a pattern and a full path, determine the pattern-mapped part.
* <p>This method is supposed to find out which part of the path is matched
* dynamically through an actual pattern, that is, it strips off a statically
* defined leading path from the given full path, returning only the actually
* pattern-matched part of the path.
* <p>For example: For "myroot/*.html" as pattern and "myroot/myfile.html"
* as full path, this method should return "myfile.html". The detailed
* determination rules are specified to this PathMatcher's matching strategy.
* <p>A simple implementation may return the given full path as-is in case
* of an actual pattern, and the empty String in case of the pattern not
* containing any dynamic parts (i.e. the <code>pattern</code> parameter being
* a static path that wouldn't qualify as an actual {@link #isPattern pattern}).
* A sophisticated implementation will differentiate between the static parts
* and the dynamic parts of the given path pattern.
* @param pattern the path pattern
* @param path the full path to introspect
* @return the pattern-mapped part of the given <code>path</code>
* (never <code>null</code>)
*/
String extractPathWithinPattern(String pattern, String path);
/**
* Given a pattern and a full path, extract the URI template variables. URI template
* variables are expressed through curly brackets ('{' and '}').
*
* <p>For example: For pattern "/hotels/{hotel}" and path "/hotels/1", this method will
* return a map containing "hotel"->"1".
*
* @param pattern the path pattern, possibly containing URI templates
* @param path the full path to extract template variables from
* @return a map, containing variable names as keys; variables values as values
*/
Map<String, String> extractUriTemplateVariables(String pattern, String path);
}

View File

@@ -0,0 +1,38 @@
package org.springframework.roo.support.api;
import java.util.logging.Logger;
/**
* Interface defining an add-on search service.
*
* <p>
* This interface is included in the support module because several of Roo's core
* infrastructure modules require add-on search capabilities.
*
* @author Ben Alex
* @author Stefan Schmidt
* @since 1.1.1
*/
public interface AddOnSearch {
/**
* Search all add-ons presently known this Roo instance, including add-ons which have
* not been downloaded or installed by the user.
*
* <p>
* Information is optionally emitted to the console via {@link Logger#info}.
*
* @param showFeedback if false will never output any messages to the console (required)
* @param searchTerms comma separated list of search terms (required)
* @param refresh attempt a fresh download of roobot.xml (optional)
* @param linesPerResult maximum number of lines per add-on (optional)
* @param maxResults maximum number of results to display (optional)
* @param trustedOnly display only trusted add-ons in search results (optional)
* @param compatibleOnly display only compatible add-ons in search results (optional)
* @param communityOnly display only community-provided add-ons in search results (optional)
* @param requiresCommand display only add-ons which offer the specified command (optional)
* @return the total number of matches found, even if only some of these are displayed due to maxResults
* (or null if the add-on list is unavailable for some reason, eg network problems etc)
*/
Integer searchAddOns(boolean showFeedback, String searchTerms, boolean refresh, int linesPerResult, int maxResults, boolean trustedOnly, boolean compatibleOnly, boolean communityOnly, String requiresCommand);
}

View File

@@ -0,0 +1,128 @@
package org.springframework.roo.support.logging;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.springframework.roo.support.util.Assert;
/**
* Defers the publication of JDK {@link LogRecord} instances until a target {@link Handler} is registered.
*
* <p>
* This class is useful if a target {@link Handler} cannot be instantiated before {@link LogRecord} instances are being
* published. This may be the case if the target {@link Handler} requires the establishment of complex publication
* infrastructure such as a GUI, message queue, IoC container and the establishment of that infrastructure may produce
* log messages that should ultimately be delivered to the target {@link Handler}.
*
* <p>
* In recognition that sometimes the target {@link Handler} may never be registered (perhaps due to failures configuring
* its supporting infrastructure), this class supports a fallback mode. When in fallback mode, a fallback {@link Handler}
* will receive all previous and future {@link LogRecord} instances. Fallback mode is automatically triggered if a
* {@link LogRecord} is published at the fallback {@link Level}. Fallback mode is also triggered if the {@link #flush()}
* or {@link #close()} method is involved and the target {@link Handler} has never been registered.
*
* @author Ben Alex
* @since 1.0
*/
public class DeferredLogHandler extends Handler {
// Fields
private final List<LogRecord> logRecords = Collections.synchronizedList(new ArrayList<LogRecord>());
private final Handler fallbackHandler;
private final Level fallbackPushLevel;
private boolean fallbackMode = false;
private Handler targetHandler;
/**
* Creates an instance that will publish all recorded {@link LogRecord} instances to the specified fallback
* {@link Handler} if an event of the specified {@link Level} is received.
*
* @param fallbackHandler to publish events to (mandatory)
* @param fallbackPushLevel the level which will trigger an event publication (mandatory)
*/
public DeferredLogHandler(final Handler fallbackHandler, final Level fallbackPushLevel) {
Assert.notNull(fallbackHandler, "Fallback handler required");
Assert.notNull(fallbackPushLevel, "Fallback push level required");
this.fallbackHandler = fallbackHandler;
this.fallbackPushLevel = fallbackPushLevel;
}
@Override
public void close() throws SecurityException {
if (targetHandler == null) {
fallbackMode = true;
}
if (fallbackMode) {
publishLogRecordsTo(fallbackHandler);
fallbackHandler.close();
return;
}
targetHandler.close();
}
@Override
public void flush() {
if (targetHandler == null) {
fallbackMode = true;
}
if (fallbackMode) {
publishLogRecordsTo(fallbackHandler);
fallbackHandler.flush();
return;
}
targetHandler.flush();
}
/**
* Stores the log record internally.
*/
@Override
public void publish(final LogRecord record) {
if (!isLoggable(record)) {
return;
}
if (fallbackMode) {
fallbackHandler.publish(record);
return;
}
if (targetHandler != null) {
targetHandler.publish(record);
return;
}
synchronized (logRecords) {
logRecords.add(record);
}
if (!fallbackMode && record.getLevel().intValue() >= fallbackPushLevel.intValue()) {
fallbackMode = true;
publishLogRecordsTo(fallbackHandler);
}
}
/**
* @return the target {@link Handler}, or null if there is no target {@link Handler} defined so far
*/
public Handler getTargetHandler() {
return targetHandler;
}
public void setTargetHandler(final Handler targetHandler) {
Assert.notNull(targetHandler, "Must specify a target handler");
this.targetHandler = targetHandler;
if (!fallbackMode) {
publishLogRecordsTo(this.targetHandler);
}
}
private void publishLogRecordsTo(final Handler destination) {
synchronized (logRecords) {
for (LogRecord record : logRecords) {
destination.publish(record);
}
logRecords.clear();
}
}
}

View File

@@ -0,0 +1,148 @@
package org.springframework.roo.support.logging;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.StringUtils;
/**
* Utility methods for dealing with {@link Handler} objects.
*
* @author Ben Alex
* @since 1.0
*
*/
public abstract class HandlerUtils {
/**
* Obtains a {@link Logger} that guarantees to set the {@link Level}
* to {@link Level#FINE} if it is part of org.springframework.roo.
* Unfortunately this is needed due to a regression in JDK 1.6.0_18
* as per issue ROO-539.
*
* @param clazz to retrieve the logger for (required)
* @return the logger, which will at least of {@link Level#FINE} if no level was specified
*/
public static Logger getLogger(final Class<?> clazz) {
Assert.notNull(clazz, "Class required");
Logger logger = Logger.getLogger(clazz.getName());
if (logger.getLevel() == null && clazz.getName().startsWith("org.springframework.roo")) {
logger.setLevel(Level.FINE);
}
return logger;
}
/**
* Replaces each {@link Handler} defined against the presented {@link Logger} with {@link DeferredLogHandler}.
*
* <p>
* This is useful for ensuring any {@link Handler} defaults defined by the user are preserved and treated as the
* {@link DeferredLogHandler} "fallback" {@link Handler} if the indicated severity {@link Level} is encountered.
*
* <p>
* This method will create a {@link ConsoleHandler} if the presented {@link Logger} has no current {@link Handler}.
*
* @param logger to introspect and replace the {@link Handler}s for (required)
* @param fallbackSeverity to trigger fallback mode (required)
* @return the number of {@link DeferredLogHandler}s now registered against the {@link Logger} (guaranteed to be 1 or above)
*/
public static int wrapWithDeferredLogHandler(final Logger logger, final Level fallbackSeverity) {
Assert.notNull(logger, "Logger is required");
Assert.notNull(fallbackSeverity, "Fallback severity is required");
List<DeferredLogHandler> newHandlers = new ArrayList<DeferredLogHandler>();
// Create DeferredLogHandlers for each Handler in presented Logger
Handler[] handlers = logger.getHandlers();
if (handlers != null && handlers.length > 0) {
for (Handler h : handlers) {
logger.removeHandler(h);
newHandlers.add(new DeferredLogHandler(h, fallbackSeverity));
}
}
// Create a default DeferredLogHandler if no Handler was defined in the presented Logger
if (newHandlers.isEmpty()) {
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new Formatter() {
@Override
public String format(final LogRecord record) {
return record.getMessage() + StringUtils.LINE_SEPARATOR;
}
});
newHandlers.add(new DeferredLogHandler(consoleHandler, fallbackSeverity));
}
// Add the new DeferredLogHandlers to the presented Logger
for (DeferredLogHandler h : newHandlers) {
logger.addHandler(h);
}
return newHandlers.size();
}
/**
* Registers the presented target {@link Handler} against any {@link DeferredLogHandler} encountered in the presented
* {@link Logger}.
*
* <p>
* Generally this method is used on {@link Logger} instances that have previously been presented to the
* {@link #wrapWithDeferredLogHandler(Logger, Level)} method.
*
* <p>
* The method will return a count of how many {@link DeferredLogHandler} instances it detected. Note that no
* attempt is made to distinguish between instances already possessing the intended target {@link Handler}
* or those already possessing any target {@link Handler} at all. This method always overwrites the target
* {@link Handler} and the returned count represents how many overwrites took place.
*
* @param logger to introspect for {@link DeferredLogHandler} instances (required)
* @param target to set as the target {@link Handler}
* @return number of {@link DeferredLogHandler} instances detected and updated (may be 0 if none found)
*/
public static int registerTargetHandler(final Logger logger, final Handler target) {
Assert.notNull(logger, "Logger is required");
Assert.notNull(target, "Target handler is required");
int replaced = 0;
Handler[] handlers = logger.getHandlers();
if (handlers != null && handlers.length > 0) {
for (Handler h : handlers) {
if (h instanceof DeferredLogHandler) {
replaced++;
DeferredLogHandler defLogger = (DeferredLogHandler) h;
defLogger.setTargetHandler(target);
}
}
}
return replaced;
}
/**
* Forces all {@link Handler} instances registered in the presented {@link Logger} to be flushed.
*
* @param logger to flush (required)
* @return the number of {@link Handler}s flushed (may be 0 or above)
*/
public static int flushAllHandlers(final Logger logger) {
Assert.notNull(logger, "Logger is required");
int flushed = 0;
Handler[] handlers = logger.getHandlers();
if (handlers != null && handlers.length > 0) {
for (Handler h : handlers) {
flushed++;
h.flush();
}
}
return flushed;
}
}

View File

@@ -0,0 +1,75 @@
package org.springframework.roo.support.logging;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.IOUtils;
/**
* Wraps an {@link OutputStream} and automatically passes each line to the {@link Logger}
* when {@link OutputStream#flush()} or {@link OutputStream#close()} is called.
*
* @author Ben Alex
* @since 1.1
*/
public class LoggingOutputStream extends OutputStream {
// Constants
protected static final Logger LOGGER = HandlerUtils.getLogger(LoggingOutputStream.class);
// Fields
private final Level level;
private String sourceClassName = LoggingOutputStream.class.getName();
private int count;
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
/**
* Constructor
*
* @param level the level at which to log (required)
*/
public LoggingOutputStream(final Level level) {
Assert.notNull(level, "A logging level is required");
this.level = level;
}
@Override
public void write(final int b) throws IOException {
baos.write(b);
count++;
}
@Override
public void flush() throws IOException {
if (count > 0) {
String msg = new String(baos.toByteArray());
LogRecord record = new LogRecord(level, msg);
record.setSourceClassName(sourceClassName);
try {
LOGGER.log(record);
} finally {
count = 0;
IOUtils.closeQuietly(baos);
baos = new ByteArrayOutputStream();
}
}
}
@Override
public void close() throws IOException {
flush();
}
public String getSourceClassName() {
return sourceClassName;
}
public void setSourceClassName(final String sourceClassName) {
this.sourceClassName = sourceClassName;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* 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.springframework.roo.support.style;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.ClassUtils;
import org.springframework.roo.support.util.ObjectUtils;
/**
* Spring's default <code>toString()</code> styler.
*
* <p>This class is used by {@link ToStringCreator} to style <code>toString()</code>
* output in a consistent manner according to Spring conventions.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 1.2.2
*/
public class DefaultToStringStyler implements ToStringStyler {
// Fields
private final ValueStyler valueStyler;
/**
* Create a new DefaultToStringStyler.
* @param valueStyler the ValueStyler to use
*/
public DefaultToStringStyler(final ValueStyler valueStyler) {
Assert.notNull(valueStyler, "ValueStyler must not be null");
this.valueStyler = valueStyler;
}
/**
* Return the ValueStyler used by this ToStringStyler.
*/
protected final ValueStyler getValueStyler() {
return this.valueStyler;
}
public void styleStart(final StringBuilder buffer, final Object obj) {
if (!obj.getClass().isArray()) {
buffer.append('[').append(ClassUtils.getShortName(obj.getClass()));
styleIdentityHashCode(buffer, obj);
}
else {
buffer.append('[');
styleIdentityHashCode(buffer, obj);
buffer.append(' ');
styleValue(buffer, obj);
}
}
private void styleIdentityHashCode(final StringBuilder buffer, final Object obj) {
buffer.append('@');
buffer.append(ObjectUtils.getIdentityHexString(obj));
}
public void styleEnd(final StringBuilder buffer, final Object o) {
buffer.append(']');
}
public void styleField(final StringBuilder buffer, final String fieldName, final Object value) {
styleFieldStart(buffer, fieldName);
styleValue(buffer, value);
styleFieldEnd(buffer, fieldName);
}
protected void styleFieldStart(final StringBuilder buffer, final String fieldName) {
buffer.append(' ').append(fieldName).append(" = ");
}
protected void styleFieldEnd(final StringBuilder buffer, final String fieldName) {
}
public void styleValue(final StringBuilder buffer, final Object value) {
buffer.append(this.valueStyler.style(value));
}
public void styleFieldSeparator(final StringBuilder buffer) {
buffer.append(',');
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* 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.springframework.roo.support.style;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.roo.support.util.ClassUtils;
import org.springframework.roo.support.util.ObjectUtils;
/**
* Converts objects to String form, generally for debugging purposes,
* using Spring's <code>toString</code> styling conventions.
*
* <p>Uses the reflective visitor pattern underneath the hood to nicely
* encapsulate styling algorithms for each type of styled object.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 1.2.2
*/
public class DefaultValueStyler implements ValueStyler {
private static final String EMPTY = "[empty]";
private static final String NULL = "[null]";
private static final String COLLECTION = "collection";
private static final String SET = "set";
private static final String LIST = "list";
private static final String MAP = "map";
private static final String ARRAY = "array";
public String style(final Object value) {
if (value == null) {
return NULL;
} else if (value instanceof String) {
return "\'" + value + "\'";
} else if (value instanceof Class<?>) {
return ClassUtils.getShortName((Class<?>) value);
} else if (value instanceof Method) {
Method method = (Method) value;
return method.getName() + "@" + ClassUtils.getShortName(method.getDeclaringClass());
} else if (value instanceof Map<?, ?>) {
return style((Map<?, ?>) value);
} else if (value instanceof Map.Entry<?, ?>) {
return style((Map.Entry<?, ?>) value);
} else if (value instanceof Collection<?>) {
return style((Collection<?>) value);
} else if (value.getClass().isArray()) {
return styleArray(ObjectUtils.toObjectArray(value));
} else {
return String.valueOf(value);
}
}
private String style(final Map<?, ?> value) {
StringBuilder result = new StringBuilder(value.size() * 8 + 16);
result.append(MAP + "[");
for (Iterator<?> it = value.entrySet().iterator(); it.hasNext();) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
result.append(style(entry));
if (it.hasNext()) {
result.append(',').append(' ');
}
}
if (value.isEmpty()) {
result.append(EMPTY);
}
result.append("]");
return result.toString();
}
private String style(final Map.Entry<?, ?> value) {
return style(value.getKey()) + " -> " + style(value.getValue());
}
private String style(final Collection<?> value) {
StringBuilder result = new StringBuilder(value.size() * 8 + 16);
result.append(getCollectionTypeString(value)).append('[');
for (Iterator<?> i = value.iterator(); i.hasNext();) {
result.append(style(i.next()));
if (i.hasNext()) {
result.append(',').append(' ');
}
}
if (value.isEmpty()) {
result.append(EMPTY);
}
result.append("]");
return result.toString();
}
private String getCollectionTypeString(final Collection<?> value) {
if (value instanceof List<?>) {
return LIST;
} else if (value instanceof Set<?>) {
return SET;
} else {
return COLLECTION;
}
}
private String styleArray(final Object[] array) {
StringBuilder result = new StringBuilder(array.length * 8 + 16);
result.append(ARRAY + "<").append(ClassUtils.getShortName(array.getClass().getComponentType())).append(">[");
for (int i = 0; i < array.length - 1; i++) {
result.append(style(array[i]));
result.append(',').append(' ');
}
if (array.length > 0) {
result.append(style(array[array.length - 1]));
} else {
result.append(EMPTY);
}
result.append("]");
return result.toString();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* 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.springframework.roo.support.style;
/**
* Simple utility class to allow for convenient access to value
* styling logic, mainly to support descriptive logging messages.
*
* <p>For more sophisticated needs, use the {@link ValueStyler} abstraction
* directly. This class simply uses a shared {@link DefaultValueStyler}
* instance underneath.
*
* @author Keith Donald
* @since 1.2.2
* @see ValueStyler
* @see DefaultValueStyler
*/
public abstract class StylerUtils {
/**
* Default ValueStyler instance used by the <code>style</code> method.
* Also available for the {@link ToStringCreator} class in this package.
*/
static final ValueStyler DEFAULT_VALUE_STYLER = new DefaultValueStyler();
/**
* Style the specified value according to default conventions.
* @param value the Object value to style
* @return the styled String
* @see DefaultValueStyler
*/
public static String style(final Object value) {
return DEFAULT_VALUE_STYLER.style(value);
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* 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.springframework.roo.support.style;
import org.springframework.roo.support.util.Assert;
/**
* Utility class that builds pretty-printing <code>toString()</code> methods
* with pluggable styling conventions. By default, ToStringCreator adheres
* to Spring's <code>toString()</code> styling conventions.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 1.2.2
*/
public class ToStringCreator {
/**
* Default ToStringStyler instance used by this ToStringCreator.
*/
private static final ToStringStyler DEFAULT_TO_STRING_STYLER = new DefaultToStringStyler(StylerUtils.DEFAULT_VALUE_STYLER);
// Fields
private final StringBuilder buffer = new StringBuilder(512);
private final ToStringStyler styler;
private final Object object;
private boolean styledFirstField;
/**
* Create a ToStringCreator for the given object.
*
* @param obj the object to be stringified
*/
public ToStringCreator(final Object obj) {
this(obj, (ToStringStyler) null);
}
/**
* Create a ToStringCreator for the given object, using the provided style.
*
* @param obj the object to be stringified
* @param styler the ValueStyler encapsulating pretty-print instructions
*/
public ToStringCreator(final Object obj, final ValueStyler styler) {
this(obj, new DefaultToStringStyler(styler != null ? styler : StylerUtils.DEFAULT_VALUE_STYLER));
}
/**
* Create a ToStringCreator for the given object, using the provided style.
*
* @param obj the object to be stringified
* @param styler the ToStringStyler encapsulating pretty-print instructions
*/
public ToStringCreator(final Object obj, final ToStringStyler styler) {
Assert.notNull(obj, "The object to be styled must not be null");
this.object = obj;
this.styler = (styler != null ? styler : DEFAULT_TO_STRING_STYLER);
this.styler.styleStart(this.buffer, this.object);
}
/**
* Append a byte field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final byte value) {
return append(fieldName, Byte.valueOf(value));
}
/**
* Append a short field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final short value) {
return append(fieldName, Short.valueOf(value));
}
/**
* Append a integer field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final int value) {
return append(fieldName, Integer.valueOf(value));
}
/**
* Append a long field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final long value) {
return append(fieldName, Long.valueOf(value));
}
/**
* Append a float field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final float value) {
return append(fieldName, new Float(value));
}
/**
* Append a double field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final double value) {
return append(fieldName, new Double(value));
}
/**
* Append a boolean field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final boolean value) {
return append(fieldName, Boolean.valueOf(value));
}
/**
* Append a field value.
*
* @param fieldName the name of the field, usually the member variable name
* @param value the field value; can be <code>null</code>
* @return this, to support call-chaining
*/
public ToStringCreator append(final String fieldName, final Object value) {
printFieldSeparatorIfNecessary();
this.styler.styleField(this.buffer, fieldName, value);
return this;
}
private void printFieldSeparatorIfNecessary() {
if (this.styledFirstField) {
this.styler.styleFieldSeparator(this.buffer);
}
else {
this.styledFirstField = true;
}
}
/**
* Append the provided value.
*
* @param value The value to append
* @return this, to support call-chaining.
*/
public ToStringCreator append(final Object value) {
this.styler.styleValue(this.buffer, value);
return this;
}
/**
* Return the String representation that this ToStringCreator built.
*/
@Override
public String toString() {
this.styler.styleEnd(this.buffer, this.object);
return this.buffer.toString();
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* 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.springframework.roo.support.style;
/**
* A strategy interface for pretty-printing <code>toString()</code> methods.
* Encapsulates the print algorithms; some other object such as a builder
* should provide the workflow.
*
* @author Keith Donald
* @since 1.2.2
*/
public interface ToStringStyler {
/**
* Style a <code>toString()</code>'ed object before its fields are styled.
*
* @param buffer the buffer to print to
* @param obj the object to style; can be <code>null</code>
*/
void styleStart(StringBuilder buffer, Object obj);
/**
* Style a <code>toString()</code>'ed object after it's fields are styled.
*
* @param buffer the buffer to print to
* @param obj the object to style; can be <code>null</code>
*/
void styleEnd(StringBuilder buffer, Object obj);
/**
* Style a field value as a string.
*
* @param buffer the buffer to print to
* @param fieldName the he name of the field
* @param value the field value; can be <code>null</code>
*/
void styleField(StringBuilder buffer, String fieldName, Object value);
/**
* Style the given value.
*
* @param buffer the buffer to print to
* @param value the field value; can be <code>null</code>
*/
void styleValue(StringBuilder buffer, Object value);
/**
* Style the field separator.
*
* @param buffer buffer to print to
*/
void styleFieldSeparator(StringBuilder buffer);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* 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.springframework.roo.support.style;
/**
* Strategy that encapsulates value String styling algorithms
* according to Spring conventions.
*
* @author Keith Donald
* @since 1.2.2
*/
public interface ValueStyler {
/**
* Style the given value, returning a String representation.
* @param value the Object value to style
* @return the styled String
*/
String style(Object value);
}

View File

@@ -0,0 +1,69 @@
package org.springframework.roo.support.util;
/**
* ANSI escape codes supported by JLine
*
* @author Andrew Swan
* @since 1.2.0
*/
public enum AnsiEscapeCode {
// These int literals are non-public constants in ANSIBuffer.ANSICodes
BLINK(5),
BOLD(1),
CONCEALED(8),
FG_BLACK(30),
FG_BLUE(34),
FG_CYAN(36),
FG_GREEN(32),
FG_MAGENTA(35),
FG_RED(31),
FG_YELLOW(33),
FG_WHITE(37),
OFF(0),
REVERSE(7),
UNDERSCORE(4);
// Constant for the escape character
private static final boolean ANSI_SUPPORTED = Boolean.getBoolean("roo.console.ansi");
private static final char ESC = 27;
/**
* Decorates the given text with the given escape codes (turning them off
* afterwards)
*
* @param text the text to decorate; can be <code>null</code>
* @param codes
* @return <code>null</code> if <code>null</code> is passed
*/
public static String decorate(final String text, final AnsiEscapeCode... codes) {
if (text == null || "".equals(text)) {
return text;
}
final StringBuilder sb = new StringBuilder();
if (ANSI_SUPPORTED) {
for (final AnsiEscapeCode code : codes) {
sb.append(code.code);
}
}
sb.append(text);
if (codes != null && codes.length > 0 && ANSI_SUPPORTED) {
sb.append(OFF.code);
}
return sb.toString();
}
// Fields
final String code;
/**
* Constructor
*
* @param code the numeric ANSI escape code
*/
private AnsiEscapeCode(final int code) {
// Copied from the method ANSIBuffer.ANSICodes#attrib(int)
this.code = ESC + "[" + code + "m";
}
}

View File

@@ -0,0 +1,397 @@
package org.springframework.roo.support.util;
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.util.Collection;
import java.util.Map;
/**
* Assertion utility class that assists in validating arguments.
* Useful for identifying programmer errors early and clearly at runtime.
*
* <p>For example, if the contract of a public method states it does not
* allow <code>null</code> arguments, Assert can be used to validate that
* contract. Doing this clearly indicates a contract violation when it
* occurs and protects the class's invariants.
*
* <p>Typically used to validate method arguments rather than configuration
* properties, to check for cases that are usually programmer errors rather than
* configuration errors. In contrast to config initialization code, there is
* usally no point in falling back to defaults in such methods.
*
* <p>This class is similar to JUnit's assertion library. If an argument value is
* deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
* For example:
*
* <pre class="code">
* Assert.notNull(clazz, "The class must not be null");
* Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
*
* Mainly for internal use within the framework; consider Jakarta's Commons Lang
* >= 2.0 for a more comprehensive suite of assertion utilities.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Colin Sampaleanu
* @author Rob Harrop
* @since 1.1.2
*/
public abstract class Assert {
/**
* Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre>
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if expression is <code>false</code>
*/
public static void isTrue(final boolean expression, final String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0);</pre>
* @param expression a boolean expression
* @throws IllegalArgumentException if expression is <code>false</code>
*/
public static void isTrue(final boolean expression) {
isTrue(expression, "[Assertion failed] - this expression must be true");
}
/**
* Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value, "The value must be null");</pre>
* @param object the object to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is not <code>null</code>
*/
public static void isNull(final Object object, final String message) {
if (object != null) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value);</pre>
* @param object the object to check
* @throws IllegalArgumentException if the object is not <code>null</code>
*/
public static void isNull(final Object object) {
isNull(object, "[Assertion failed] - the object argument must be null");
}
/**
* Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
* @param object the object to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static void notNull(final Object object, final String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz);</pre>
* @param object the object to check
* @throws IllegalArgumentException if the object is <code>null</code>
*/
public static void notNull(final Object object) {
notNull(object, "[Assertion failed] - this argument is required; it must not be null");
}
/**
* Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @see StringUtils#hasLength
*/
public static void hasLength(final String text, final String message) {
if (!StringUtils.hasLength(text)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name);</pre>
* @param text the String to check
* @see StringUtils#hasLength
*/
public static void hasLength(final String text) {
hasLength(text,
"[Assertion failed] - this String argument must have length; it must not be null or empty");
}
/**
* Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @see StringUtils#hasText
*/
public static void hasText(final String text, final String message) {
if (StringUtils.isBlank(text)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check
* @see StringUtils#hasText
*/
public static void hasText(final String text) {
hasText(text,
"[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
}
/**
* Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
* @param textToSearch the text to search
* @param substring the substring to find within the text
* @param message the exception message to use if the assertion fails
*/
public static void doesNotContain(final String textToSearch, final String substring, final String message) {
if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
textToSearch.indexOf(substring) != -1) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod");</pre>
* @param textToSearch the text to search
* @param substring the substring to find within the text
*/
public static void doesNotContain(final String textToSearch, final String substring) {
doesNotContain(textToSearch, substring,
"[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
}
/**
* Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
* @param array the array to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/
public static void notEmpty(final Object[] array, final String message) {
if (ObjectUtils.isEmpty(array)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array);</pre>
* @param array the array to check
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/
public static void notEmpty(final Object[] array) {
notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
}
/**
* Assert that an array has no null elements.
* Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
* @param array the array to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/
public static void noNullElements(final Object[] array, final String message) {
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (array[i] == null) {
throw new IllegalArgumentException(message);
}
}
}
}
/**
* Assert that an array has no null elements.
* Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array);</pre>
* @param array the array to check
* @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/
public static void noNullElements(final Object[] array) {
noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
}
/**
* Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
* @param collection the collection to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/
public static void notEmpty(final Collection<?> collection, final String message) {
if (CollectionUtils.isEmpty(collection)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
* @param collection the collection to check
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/
public static void notEmpty(final Collection<?> collection) {
notEmpty(collection,
"[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
}
/**
* Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry.
* <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
* @param map the map to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/
public static void notEmpty(final Map<?, ?> map, final String message) {
if (CollectionUtils.isEmpty(map)) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry.
* <pre class="code">Assert.notEmpty(map);</pre>
* @param map the map to check
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/
public static void notEmpty(final Map<?, ?> map) {
notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
}
/**
* Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
* @param clazz the required class
* @param obj the object to check
* @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance
*/
public static void isInstanceOf(final Class<?> clazz, final Object obj) {
isInstanceOf(clazz, obj, "");
}
/**
* Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
* @param type the type to check against
* @param obj the object to check
* @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it.
* @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance
*/
public static void isInstanceOf(final Class<?> type, final Object obj, final String message) {
notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) {
throw new IllegalArgumentException(message +
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
"] must be an instance of " + type);
}
}
/**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
* @param superType the super type to check
* @param subType the sub type to check
* @throws IllegalArgumentException if the classes are not assignable
*/
public static void isAssignable(final Class<?> superType, final Class<?> subType) {
isAssignable(superType, subType, "");
}
/**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
* @param superType the super type to check against
* @param subType the sub type to check
* @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it.
* @throws IllegalArgumentException if the classes are not assignable
*/
public static void isAssignable(final Class<?> superType, final Class<?> subType, final String message) {
notNull(superType, "Type to check against must not be null");
if (subType == null || !superType.isAssignableFrom(subType)) {
throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
}
}
/**
* Assert a boolean expression, throwing <code>IllegalStateException</code>
* if the test result is <code>false</code>. Call isTrue if you wish to
* throw IllegalArgumentException on an assertion failure.
* <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalStateException if expression is <code>false</code>
*/
public static void state(final boolean expression, final String message) {
if (!expression) {
throw new IllegalStateException(message);
}
}
/**
* Assert a boolean expression, throwing {@link IllegalStateException}
* if the test result is <code>false</code>.
* <p>Call {@link #isTrue(boolean)} if you wish to
* throw {@link IllegalArgumentException} on an assertion failure.
* <pre class="code">Assert.state(id == null);</pre>
* @param expression a boolean expression
* @throws IllegalStateException if the supplied expression is <code>false</code>
*/
public static void state(final boolean expression) {
state(expression, "[Assertion failed] - this state invariant must be true");
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,367 @@
package org.springframework.roo.support.util;
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Miscellaneous collection utility methods.
* Mainly for internal use within the framework.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Andrew Swan
* @since 1.1.3
*/
public final class CollectionUtils {
/**
* Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>.
*
* @param collection the Collection to check
* @return whether the given Collection is empty
*/
public static boolean isEmpty(final Collection<?> collection) {
return (collection == null || collection.isEmpty());
}
/**
* Return <code>true</code> if the supplied Map is <code>null</code>
* or empty. Otherwise, return <code>false</code>.
*
* @param map the Map to check
* @return whether the given Map is empty
*/
public static boolean isEmpty(final Map<?, ?> map) {
return (map == null || map.isEmpty());
}
/**
* Convert the supplied array into a List. A primitive array gets
* converted into a List of the appropriate wrapper type.
* <p>A <code>null</code> source value will be converted to an
* empty List.
*
* @param source the (potentially primitive) array
* @return the converted List result
* @see ObjectUtils#toObjectArray(Object)
*/
public static List<?> arrayToList(final Object source) {
return Arrays.asList(ObjectUtils.toObjectArray(source));
}
/**
* Merge the given array into the given Collection.
*
* @param array the array to merge (may be <code>null</code>)
* @param collection the target Collection to merge the array into
*/
public static void mergeArrayIntoCollection(final Object array, final Collection<Object> collection) {
if (collection == null) {
throw new IllegalArgumentException("Collection must not be null");
}
final Object[] arr = ObjectUtils.toObjectArray(array);
for (final Object elem : arr) {
collection.add(elem);
}
}
/**
* Merge the given Properties instance into the given Map,
* copying all properties (key-value pairs) over.
* <p>Uses <code>Properties.propertyNames()</code> to even catch
* default properties linked into the original Properties instance.
*
* @param props the Properties instance to merge (may be <code>null</code>)
* @param map the target Map to merge the properties into
*/
public static void mergePropertiesIntoMap(final Properties props, final Map<String, String> map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
if (props != null) {
for (final Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) {
final String key = (String) en.nextElement();
map.put(key, props.getProperty(key));
}
}
}
/**
* Check whether the given Iterator contains the given element.
*
* @param iterator the Iterator to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean contains(final Iterator<?> iterator, final Object element) {
if (iterator != null) {
while (iterator.hasNext()) {
final Object candidate = iterator.next();
if (ObjectUtils.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
* Check whether the given Enumeration contains the given element.
*
* @param enumeration the Enumeration to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean contains(final Enumeration<?> enumeration, final Object element) {
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
final Object candidate = enumeration.nextElement();
if (ObjectUtils.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
* Check whether the given Collection contains the given element instance.
* <p>Enforces the given instance to be present, rather than returning
* <code>true</code> for an equal element as well.
*
* @param collection the Collection to check
* @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else
*/
public static boolean containsInstance(final Collection<?> collection, final Object element) {
if (collection != null) {
for (final Object candidate : collection) {
if (candidate == element) {
return true;
}
}
}
return false;
}
/**
* Return <code>true</code> if any element in '<code>candidates</code>' is
* contained in '<code>source</code>'; otherwise returns <code>false</code>.
*
* @param source the source Collection
* @param candidates the candidates to search for
* @return whether any of the candidates has been found
*/
public static boolean containsAny(final Collection<?> source, final Collection<?> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return false;
}
for (final Object candidate : candidates) {
if (source.contains(candidate)) {
return true;
}
}
return false;
}
/**
* Return the first element in '<code>candidates</code>' that is contained in
* '<code>source</code>'. If no element in '<code>candidates</code>' is present in
* '<code>source</code>' returns <code>null</code>. Iteration order is
* {@link Collection} implementation specific.
*
* @param source the source Collection
* @param candidates the candidates to search for
* @return the first present object, or <code>null</code> if not found
*/
public static Object findFirstMatch(final Collection<?> source, final Collection<?> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return null;
}
for (final Object candidate : candidates) {
if (source.contains(candidate)) {
return candidate;
}
}
return null;
}
/**
* Find a single value of the given type in the given Collection.
*
* @param collection the Collection to search
* @param type the type to look for
* @return a value of the given type found if there is a clear match,
* or <code>null</code> if none or more than one such value found
*/
@SuppressWarnings("unchecked")
public static <T> T findValueOfType(final Collection<?> collection, final Class<T> type) {
if (isEmpty(collection)) {
return null;
}
T value = null;
for (final Object element : collection) {
if (type == null || type.isInstance(element)) {
if (value != null) {
// More than one value found... no clear single value.
return null;
}
value = (T) element;
}
}
return value;
}
/**
* Find a single value of one of the given types in the given Collection:
* searching the Collection for a value of the first type, then
* searching for a value of the second type, etc.
*
* @param collection the collection to search
* @param types the types to look for, in prioritized order
* @return a value of one of the given types found if there is a clear match,
* or <code>null</code> if none or more than one such value found
*/
public static Object findValueOfType(final Collection<?> collection, final Class<?>... types) {
if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {
return null;
}
for (final Class<?> type : types) {
final Object value = findValueOfType(collection, type);
if (value != null) {
return value;
}
}
return null;
}
/**
* Determine whether the given Collection only contains a single unique object.
*
* @param collection the Collection to check
* @return <code>true</code> if the collection contains a single reference or
* multiple references to the same instance, <code>false</code> else
*/
public static boolean hasUniqueObject(final Collection<?> collection) {
if (isEmpty(collection)) {
return false;
}
boolean hasCandidate = false;
Object candidate = null;
for (final Iterator<?> it = collection.iterator(); it.hasNext();) {
final Object elem = it.next();
if (!hasCandidate) {
hasCandidate = true;
candidate = elem;
}
else if (candidate != elem) {
return false;
}
}
return true;
}
/**
* Filters (removes elements from) the given {@link Iterable} using the
* given filter.
*
* @param <T> the type of object being filtered
* @param unfiltered the iterable to filter; can be <code>null</code>
* @param filter the filter to apply; can be <code>null</code> for none
* @return a non-<code>null</code> list
*/
public static <T> List<T> filter(final Iterable<? extends T> unfiltered, final Filter<T> filter) {
final List<T> filtered = new ArrayList<T>();
if (unfiltered != null) {
for (final T element : unfiltered) {
if (filter == null || filter.include(element)) {
filtered.add(element);
}
}
}
return filtered;
}
/**
* Adds the given items to the given collection
*
* @param <T> the type of item in the collection being updated
* @param newItems the items being added; can be <code>null</code> for none
* @param existingItems the items being added to; must be modifiable
* @return <code>true</code> if the existing collection was modified
* @throws UnsupportedOperationException if there are items to add and the
* existing collection is not modifiable
* @since 1.2.0
*/
public static <T> boolean addAll(final Collection<? extends T> newItems, final Collection<T> existingItems) {
if (existingItems != null && newItems != null) {
return existingItems.addAll(newItems);
}
return false;
}
/**
* Populates the given collection by replacing any existing contents with
* the given elements, in a null-safe way.
*
* @param <T> the type of element in the collection
* @param collection the collection to populate (can be <code>null</code>)
* @param items the items with which to populate the collection (can be
* <code>null</code> or empty for none)
* @return the given collection (useful if it was anonymous)
*/
public static <T> Collection<T> populate(final Collection<T> collection, final Collection<? extends T> items) {
if (collection != null) {
collection.clear();
if (items != null) {
collection.addAll(items);
}
}
return collection;
}
/**
* Returns the first element of the given collection
*
* @param <T>
* @param collection
* @return <code>null</code> if the first element is <code>null</code> or
* the collection is <code>null</code> or empty
*/
public static <T> T firstElementOf(final Collection<? extends T> collection) {
if (isEmpty(collection)) {
return null;
}
return collection.iterator().next();
}
/**
* Constructor is private to prevent instantiation
*
* @since 1.2.0
*/
private CollectionUtils() {}
}

View File

@@ -0,0 +1,295 @@
package org.springframework.roo.support.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Convenience methods for working with the DOM API,
* in particular for working with DOM Nodes and DOM Elements.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Costin Leau
* @author Alan Stewart
* @since 1.2.0
* @see org.w3c.dom.Node
* @see org.w3c.dom.Element
*/
public final class DomUtils {
/**
* Retrieve all child elements of the given DOM element that match any of
* the given element names. Only look at the direct child level of the
* given element; do not go into further depth (in contrast to the
* DOM API's <code>getElementsByTagName</code> method).
*
* @param element the DOM element to analyze
* @param childElementNames the child element names to look for
* @return a List of child <code>org.w3c.dom.Element</code> instances
* @see org.w3c.dom.Element
* @see org.w3c.dom.Element#getElementsByTagName
*/
public static List<Element> getChildElementsByTagName(final Element element, final String[] childElementNames) {
Assert.notNull(element, "Element must not be null");
Assert.notNull(childElementNames, "Element names collection must not be null");
List<String> childEleNameList = Arrays.asList(childElementNames);
NodeList nl = element.getChildNodes();
List<Element> childEles = new ArrayList<Element>();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
childEles.add((Element) node);
}
}
return childEles;
}
/**
* Retrieve all child elements of the given DOM element that match
* the given element name. Only look at the direct child level of the
* given element; do not go into further depth (in contrast to the
* DOM API's <code>getElementsByTagName</code> method).
*
* @param element the DOM element to analyze
* @param childEleName the child element name to look for
* @return a List of child <code>org.w3c.dom.Element</code> instances
* @see org.w3c.dom.Element
* @see org.w3c.dom.Element#getElementsByTagName
*/
public static List<Element> getChildElementsByTagName(final Element element, final String childEleName) {
return getChildElementsByTagName(element, new String[] {childEleName});
}
/**
* Returns the first child element identified by its name.
*
* @param element the DOM element to analyze
* @param childElementName the child element name to look for
* @return the <code>org.w3c.dom.Element</code> instance,
* or <code>null</code> if none found
*/
public static Element getChildElementByTagName(final Element element, final String childElementName) {
Assert.notNull(element, "Element must not be null");
Assert.notNull(childElementName, "Element name must not be null");
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element && nodeNameMatch(node, childElementName)) {
return (Element) node;
}
}
return null;
}
/**
* Returns the first child element value identified by its name.
*
* @param element the DOM element to analyze
* @param childElementName the child element name to look for
* @return the extracted text value,
* or <code>null</code> if no child element found
*/
public static String getChildElementValueByTagName(final Element element, final String childElementName) {
Element child = getChildElementByTagName(element, childElementName);
return (child != null ? getTextValue(child) : null);
}
/**
* Extract the text value from the given DOM element, ignoring XML comments.
* <p>Appends all CharacterData nodes and EntityReference nodes
* into a single String value, excluding Comment nodes.
*
* @see CharacterData
* @see EntityReference
* @see Comment
*/
public static String getTextValue(final Element valueElement) {
Assert.notNull(valueElement, "Element must not be null");
StringBuilder sb = new StringBuilder();
NodeList nl = valueElement.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node item = nl.item(i);
if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
sb.append(item.getNodeValue());
}
}
return sb.toString();
}
/**
* Namespace-aware equals comparison. Returns <code>true</code> if either
* {@link Node#getLocalName} or {@link Node#getNodeName} equals <code>desiredName</code>,
* otherwise returns <code>false</code>.
*
* @param node (required)
* @param desiredName (required)
* @return
*/
public static boolean nodeNameEquals(final Node node, final String desiredName) {
Assert.notNull(node, "Node must not be null");
Assert.notNull(desiredName, "Desired name must not be null");
return nodeNameMatch(node, desiredName);
}
/**
* Matches the given node's name and local name against the given desired name.
*
* @param node
* @param desiredName
* @return
*/
private static boolean nodeNameMatch(final Node node, final String desiredName) {
return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
}
/**
* Matches the given node's name and local name against the given desired names.
*
* @param node
* @param desiredNames
* @return
*/
private static boolean nodeNameMatch(final Node node, final Collection<?> desiredNames) {
return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
}
/**
* Removes empty text nodes from the specified node.
*
* @param node the element where empty text nodes will be removed
*/
public static void removeTextNodes(final Node node) {
if (node == null) {
return;
}
final NodeList children = node.getChildNodes();
for (int i = children.getLength() - 1; i >= 0; i--) {
final Node child = children.item(i);
switch (child.getNodeType()) {
case Node.ELEMENT_NODE:
removeTextNodes(child);
break;
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
if (StringUtils.isBlank(child.getNodeValue())) {
node.removeChild(child);
}
break;
}
}
}
/**
* Returns the text content of the given {@link Node}, null safe.
*
* @param node can be <code>null</code>
* @param defaultValue the value to return if the node is <code>null</code>
* @return the given default value if the node is <code>null</code>
* @see Node#getTextContent()
* @since 1.2.0
*/
public static String getTextContent(final Node node, final String defaultValue) {
if (node == null) {
return defaultValue;
}
return node.getTextContent();
}
/**
* Creates a child element with the given name and parent. Avoids the type
* of bug whereby the developer calls {@link Document#createElement(String)}
* but forgets to append it to the relevant parent.
*
* @param tagName the name of the new child (required)
* @param parent the parent node (required)
* @param document the document to which the parent and child belong (required)
* @return the created element
* @since 1.2.0
*/
public static Element createChildElement(final String tagName, final Node parent, final Document document) {
final Element child = document.createElement(tagName);
parent.appendChild(child);
return child;
}
/**
* Returns the child node with the given tag name, creating it if it does
* not exist.
*
* @param tagName the child tag to look for and possibly create (required)
* @param parent the parent in which to look for the child (required)
* @param document the document containing the parent (required)
* @return the existing or created child (never <code>null</code>)
* @since 1.2.0
*/
public static Element createChildIfNotExists(final String tagName, final Node parent, final Document document) {
final Element existingChild = XmlUtils.findFirstElement(tagName, parent);
if (existingChild != null) {
return existingChild;
}
// No such child; add it
return createChildElement(tagName, parent, document);
}
/**
* Returns the text content of the first child of the given parent that has
* the given tag name, if any.
*
* @param parent the parent in which to search (required)
* @param child the child name for which to search (required)
* @return <code>null</code> if there is no such child, otherwise the first
* such child's text content
*/
public static String getChildTextContent(final Element parent, final String child) {
final List<Element> children = XmlUtils.findElements(child, parent);
if (children.isEmpty()) {
return null;
}
return getTextContent(children.get(0), null);
}
/**
* Checks in under a given root element whether it can find a child element
* which matches the name supplied. Returns {@link Element} if exists.
*
* @param name the Element name (required)
* @param root the parent DOM element (required)
* @return the Element if discovered
*/
public static Element findFirstElementByName(final String name, final Element root) {
Assert.hasText(name, "Element name required");
Assert.notNull(root, "Root element required");
return (Element) root.getElementsByTagName(name).item(0);
}
/**
* Removes any elements matching the given XPath expression, relative to
* the given Element
*
* @param xPath the XPath of the element(s) to remove (can be blank)
* @param searchBase the element to which the XPath expression is relative
*/
public static void removeElements(final String xPath, final Element searchBase) {
for (final Element elementToDelete : XmlUtils.findElements(xPath, searchBase)) {
final Node parentNode = elementToDelete.getParentNode();
parentNode.removeChild(elementToDelete);
removeTextNodes(parentNode);
}
}
/**
* Constructor is private to prevent instantiation
*/
private DomUtils() {}
}

View File

@@ -0,0 +1,25 @@
package org.springframework.roo.support.util;
/**
* Methods for working with exceptions.
*
* @author Ben Alex
* @since 1.0
*/
public abstract class ExceptionUtils {
/**
* Obtains the root cause of an exception, if available.
*
* @param ex to extract the root cause from (required)
* @return the root cause, or original exception is unavailable (guaranteed to never be null)
*/
public final static Throwable extractRootCause(final Throwable ex) {
Assert.notNull(ex, "An exception is required");
Throwable root = ex;
if (ex.getCause() != null) {
root = ex.getCause();
}
return root;
}
}

View File

@@ -0,0 +1,231 @@
package org.springframework.roo.support.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
/**
* Simple utility methods for file and stream copying. All copy methods use a block size of 4096 bytes, and close all affected streams when done.
*
* <p>
* Mainly for use within the framework, but also useful for application code.
*
* @author Juergen Hoeller
* @since 1.0
*/
public final class FileCopyUtils {
public static final int BUFFER_SIZE = 4096;
// ---------------------------------------------------------------------
// Copy methods for java.io.File
// ---------------------------------------------------------------------
/**
* Copy the contents of the given input File to the given output File.
*
* @param in the file to copy from
* @param out the file to copy to
* @return the number of bytes copied
* @throws IOException in case of I/O errors
*/
public static int copy(final File in, final File out) throws IOException {
Assert.notNull(in, "No input File specified");
Assert.notNull(out, "No output File specified");
return copy(new BufferedInputStream(new FileInputStream(in)), new BufferedOutputStream(new FileOutputStream(out)));
}
/**
* Copy the contents of the given byte array to the given output File.
*
* @param bytes the byte array to copy from
* @param file the file to copy to
* @throws IOException in case of I/O errors
*/
public static void copy(final byte[] bytes, final File file) throws IOException {
Assert.notNull(bytes, "No input byte array specified");
Assert.notNull(file, "No output File specified");
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
copy(in, out);
}
/**
* Copy the contents of the given input File into a new byte array.
*
* @param in the file to copy from
* @return the new byte array that has been copied to
* @throws IOException in case of I/O errors
*/
public static byte[] copyToByteArray(final File in) throws IOException {
Assert.notNull(in, "No input File specified");
return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
}
// ---------------------------------------------------------------------
// Copy methods for java.io.InputStream / java.io.OutputStream
// ---------------------------------------------------------------------
/**
* Copy the contents of the given InputStream to the given OutputStream. Closes both streams when done.
*
* @param in the stream to copy from
* @param out the stream to copy to
* @return the number of bytes copied
* @throws IOException in case of I/O errors
*/
public static int copy(InputStream in, OutputStream out) throws IOException {
Assert.notNull(in, "No InputStream specified");
Assert.notNull(out, "No OutputStream specified");
if (!(in instanceof BufferedInputStream)) {
in = new BufferedInputStream(in);
}
if (!(out instanceof BufferedOutputStream)) {
out = new BufferedOutputStream(out);
}
try {
int byteCount = 0;
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
byteCount += bytesRead;
}
out.flush();
return byteCount;
} finally {
IOUtils.closeQuietly(in, out);
}
}
/**
* Copy the contents of the given byte array to the given OutputStream. Closes the stream when done.
*
* @param bytes the byte array to copy from
* @param out the OutputStream to copy to
* @throws IOException in case of I/O errors
*/
public static void copy(final byte[] bytes, final OutputStream out) throws IOException {
Assert.notNull(bytes, "No input byte array specified");
Assert.notNull(out, "No OutputStream specified");
try {
out.write(bytes);
} finally {
IOUtils.closeQuietly(out);
}
}
/**
* Copy the contents of the given InputStream into a new byte array. Closes the stream when done.
*
* @param in the stream to copy from
* @return the new byte array that has been copied to
* @throws IOException in case of I/O errors
*/
public static byte[] copyToByteArray(final InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
copy(in, out);
return out.toByteArray();
}
// ---------------------------------------------------------------------
// Copy methods for java.io.Reader / java.io.Writer
// ---------------------------------------------------------------------
/**
* Copy the contents of the given Reader to the given Writer. Closes both when done.
*
* @param in the Reader to copy from
* @param out the Writer to copy to
* @return the number of characters copied
* @throws IOException in case of I/O errors
*/
public static int copy(Reader in, Writer out) throws IOException {
Assert.notNull(in, "No Reader specified");
Assert.notNull(out, "No Writer specified");
if (!(in instanceof BufferedReader)) {
in = new BufferedReader(in);
}
if (!(out instanceof BufferedWriter)) {
out = new BufferedWriter(out);
}
try {
int byteCount = 0;
char[] buffer = new char[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
byteCount += bytesRead;
}
out.flush();
return byteCount;
} finally {
IOUtils.closeQuietly(in, out);
}
}
/**
* Copy the contents of the given String to the given output Writer. Closes the writer when done.
*
* @param in the String to copy from
* @param out the Writer to copy to
* @throws IOException in case of I/O errors
*/
public static void copy(final String in, Writer out) throws IOException {
Assert.notNull(in, "No input String specified");
Assert.notNull(out, "No Writer specified");
if (!(out instanceof BufferedWriter)) {
out = new BufferedWriter(out);
}
try {
out.write(in);
} finally {
IOUtils.closeQuietly(out);
}
}
/**
* Copy the contents of the given Reader into a String. Closes the reader when done.
*
* @param in the reader to copy from
* @return the String that has been copied to
* @throws IOException in case of I/O errors
*/
public static String copyToString(final Reader in) throws IOException {
StringWriter out = new StringWriter();
copy(in, out);
return out.toString();
}
/**
* Returns the contents of the given File as a String.
* <p>
* Consider using {@link FileUtils#read(File)} instead if any
* {@link IOException}s would be unrecoverable.
*
* @param file the file to read from
* @return the contents
* @throws IOException in case of I/O errors
*/
public static String copyToString(final File file) throws IOException {
return copyToString(new BufferedReader(new FileReader(file)));
}
/**
* Constructor is private to prevent instantiation
*/
private FileCopyUtils() {}
}

View File

@@ -0,0 +1,357 @@
package org.springframework.roo.support.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.regex.Pattern;
import org.springframework.roo.support.ant.AntPathMatcher;
import org.springframework.roo.support.ant.PathMatcher;
/**
* Utilities for handling {@link File} instances.
*
* @author Ben Alex
* @since 1.0
*/
public final class FileUtils {
// Constants
private static final String BACKSLASH = "\\";
private static final String ESCAPED_BACKSLASH = "\\\\";
/**
* The relative file path to the current directory. Should be valid on all
* platforms that Roo supports.
*/
public static final String CURRENT_DIRECTORY = ".";
private static final String WINDOWS_DRIVE_PREFIX = "^[A-Za-z]:";
// Doesn't check for backslash after the colon, since Java has no issues with paths like c:/Windows
private static final Pattern WINDOWS_DRIVE_PATH = Pattern.compile(WINDOWS_DRIVE_PREFIX + ".*");
private static final PathMatcher PATH_MATCHER;
static {
PATH_MATCHER = new AntPathMatcher();
((AntPathMatcher) PATH_MATCHER).setPathSeparator(File.separator);
}
/**
* Deletes the specified {@link File}.
*
* <p>
* If the {@link File} refers to a directory, any contents of that directory (including other directories)
* are also deleted.
*
* <p>
* If the {@link File} does not already exist, this method immediately returns true.
*
* @param file to delete (required; the file may or may not exist)
* @return true if the file is fully deleted, or false if there was a failure when deleting
*/
public static boolean deleteRecursively(final File file) {
Assert.notNull(file, "File to delete required");
if (!file.exists()) {
return true;
}
if (file.isDirectory()) {
for (File f : file.listFiles()) {
if (!deleteRecursively(f)) {
return false;
}
}
}
file.delete();
return true;
}
/**
* Copies the specified source directory to the destination.
*
* <p>
* Both the source must exist. If the destination does not already exist, it will be created. If the destination
* does exist, it must be a directory (not a file).
*
* @param source the already-existing source directory (required)
* @param destination the destination directory (required)
* @param deleteDestinationOnExit indicates whether to mark any created destinations for deletion on exit
* @return true if the copy was successful
*/
public static boolean copyRecursively(final File source, final File destination, final boolean deleteDestinationOnExit) {
Assert.notNull(source, "Source directory required");
Assert.notNull(destination, "Destination directory required");
Assert.isTrue(source.exists(), "Source directory '" + source + "' must exist");
Assert.isTrue(source.isDirectory(), "Source directory '" + source + "' must be a directory");
if (destination.exists()) {
Assert.isTrue(destination.isDirectory(), "Destination directory '" + destination + "' must be a directory");
} else {
destination.mkdirs();
if (deleteDestinationOnExit) {
destination.deleteOnExit();
}
}
for (File s : source.listFiles()) {
File d = new File(destination, s.getName());
if (deleteDestinationOnExit) {
d.deleteOnExit();
}
if (s.isFile()) {
try {
FileCopyUtils.copy(s, d);
} catch (IOException ioe) {
return false;
}
} else {
// It's a sub-directory, so copy it
d.mkdir();
if (!copyRecursively(s, d, deleteDestinationOnExit)) {
return false;
}
}
}
return true;
}
/**
* Checks if the provided fileName denotes an absolute path on the file system.
* On Windows, this includes both paths with and without drive letters, where the latter have to start with '\'.
* No check is performed to see if the file actually exists!
*
* @param fileName name of a file, which could be an absolute path
* @return true if the fileName looks like an absolute path for the current OS
*/
public static boolean denotesAbsolutePath(final String fileName) {
if (OsUtils.isWindows()) {
// first check for drive letter
if (WINDOWS_DRIVE_PATH.matcher(fileName).matches()) {
return true;
}
}
return fileName.startsWith(File.separator);
}
/**
* Returns the part of the given path that represents a directory, in other
* words the given path if it's already a directory, or the parent directory
* if it's a file.
*
* @param fileIdentifier the path to parse (required)
* @return see above
* @since 1.2.0
*/
public static String getFirstDirectory(String fileIdentifier) {
fileIdentifier = removeTrailingSeparator(fileIdentifier);
if (new File(fileIdentifier).isDirectory()) {
return fileIdentifier;
}
return backOneDirectory(fileIdentifier);
}
/**
* Returns the given file system path minus its last element
*
* @param fileIdentifier
* @return
* @since 1.2.0
*/
public static String backOneDirectory(String fileIdentifier) {
fileIdentifier = removeTrailingSeparator(fileIdentifier);
fileIdentifier = fileIdentifier.substring(0, fileIdentifier.lastIndexOf(File.separator));
return removeTrailingSeparator(fileIdentifier);
}
/**
* Removes any trailing {@link File#separator}s from the given path
*
* @param path the path to modify (can be <code>null</code>)
* @return the modified path
* @since 1.2.0
*/
public static String removeTrailingSeparator(String path) {
while (path != null && path.endsWith(File.separator)) {
path = StringUtils.removeSuffix(path, File.separator);
}
return path;
}
/**
* Indicates whether the given canonical path matches the given Ant-style pattern
*
* @param antPattern the pattern to check against (can't be blank)
* @param canonicalPath the path to check (can't be blank)
* @return see above
* @since 1.2.0
*/
public static boolean matchesAntPath(final String antPattern, final String canonicalPath) {
Assert.hasText(antPattern, "Ant pattern required");
Assert.hasText(canonicalPath, "Canonical path required");
return PATH_MATCHER.match(antPattern, canonicalPath);
}
/**
* Removes any leading or trailing {@link File#separator}s from the given path.
*
* @param path the path to modify (can be <code>null</code>)
* @return the path, modified as above, or <code>null</code> if <code>null</code> was given
* @since 1.2.0
*/
public static String removeLeadingAndTrailingSeparators(String path) {
if (StringUtils.isBlank(path)) {
return path;
}
while (path.endsWith(File.separator)) {
path = StringUtils.removeSuffix(path, File.separator);
}
while (path.startsWith(File.separator)) {
path = StringUtils.removePrefix(path, File.separator);
}
return path;
}
/**
* Ensures that the given path has exactly one trailing {@link File#separator}
*
* @param path the path to modify (can't be <code>null</code>)
* @return the normalised path
* @since 1.2.0
*/
public static String ensureTrailingSeparator(final String path) {
Assert.notNull(path);
return removeTrailingSeparator(path) + File.separatorChar;
}
/**
* Returns an operating-system-dependent path consisting of the given
* elements, separated by {@link File#separator}.
*
* @param pathElements the path elements from uppermost downwards (can't be empty)
* @return a non-blank string
* @since 1.2.0
*/
public static String getSystemDependentPath(final String... pathElements) {
return getSystemDependentPath(Arrays.asList(pathElements));
}
/**
* Returns an operating-system-dependent path consisting of the given
* elements, separated by {@link File#separator}.
*
* @param pathElements the path elements from uppermost downwards (can't be empty)
* @return a non-blank string
* @since 1.2.0
*/
public static String getSystemDependentPath(final Collection<String> pathElements) {
Assert.notEmpty(pathElements);
return StringUtils.collectionToDelimitedString(pathElements, File.separator);
}
/**
* Returns the canonical path of the given {@link File}.
*
* @param file the file for which to find the canonical path (can be <code>null</code>)
* @return the canonical path, or <code>null</code> if a <code>null</code> file is given
* @since 1.2.0
*/
public static String getCanonicalPath(final File file) {
if (file == null) {
return null;
}
try {
return file.getCanonicalPath();
} catch (final IOException ioe) {
throw new IllegalStateException("Cannot determine canonical path for '" + file + "'", ioe);
}
}
/**
* Returns the platform-specific file separator as a regular expression.
*
* @return a non-blank regex
* @since 1.2.0
*/
public static String getFileSeparatorAsRegex() {
final String fileSeparator = File.separator;
if (fileSeparator.contains(BACKSLASH)) {
// Escape the backslashes
return fileSeparator.replace(BACKSLASH, ESCAPED_BACKSLASH);
}
return fileSeparator;
}
/**
* Determines the path to the requested file, relative to the given class.
*
* @param loadingClass the class to whose package the given file is relative (required)
* @param relativeFilename the name of the file relative to that package (required)
* @return the full classloader-specific path to the file (never <code>null</code>)
* @since 1.2.0
*/
public static String getPath(final Class<?> loadingClass, final String relativeFilename) {
Assert.notNull(loadingClass, "Loading class required");
Assert.hasText(relativeFilename, "Filename required");
Assert.isTrue(!relativeFilename.startsWith("/"), "Filename shouldn't start with a slash");
// Slashes instead of File.separatorChar is correct here, as these are classloader paths (not file system paths)
return "/" + loadingClass.getPackage().getName().replace('.', '/') + "/" + relativeFilename;
}
/**
* Loads the given file from the classpath.
*
* @param loadingClass the class from whose package to load the file (required)
* @param filename the name of the file to load, relative to that package (required)
* @return the file's input stream (never <code>null</code>)
* @throws IllegalArgumentException if the given file cannot be found
*/
public static File getFile(final Class<?> loadingClass, final String filename) {
final URL url = loadingClass.getResource(filename);
Assert.notNull(url, "Could not locate '" + filename + "' in classpath of " + loadingClass.getName());
try {
return new File(url.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Loads the given file from the classpath.
*
* @param loadingClass the class from whose package to load the file (required)
* @param filename the name of the file to load, relative to that package (required)
* @return the file's input stream (never <code>null</code>)
* @throws IllegalArgumentException if the given file cannot be found
*/
public static InputStream getInputStream(final Class<?> loadingClass, final String filename) {
final InputStream inputStream = loadingClass.getResourceAsStream(filename);
Assert.notNull(inputStream, "Could not locate '" + filename + "' in classpath of " + loadingClass.getName());
return inputStream;
}
/**
* Returns the contents of the given File as a String.
*
* @param file the file to read from (must be an existing file)
* @return the contents
* @throws IllegalStateException in case of I/O errors
* @since 1.2.0
*/
public static String read(final File file) {
try {
return FileCopyUtils.copyToString(file);
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Constructor is private to prevent instantiation
*
* @since 1.2.0
*/
private FileUtils() {}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.roo.support.util;
/**
* Allows filtering of objects of type T.
*
* @author Andrew Swan
* @since 1.2.0
* @param <T> the type of object to be filtered
*/
public interface Filter<T> {
/**
* Indicates whether to include the given instance in the filtered results
*
* @param type the type to evaluate; can be <code>null</code>
* @return <code>false</code> to exclude the given type
*/
boolean include(T instance);
}

View File

@@ -0,0 +1,41 @@
package org.springframework.roo.support.util;
/**
* Encodes a given byte array as hex.
*
* <p>
* Most methods in this class were obtained from the Spring Security class,
* org.springframework.security.core.codec.Hex. Spring Security is licensed
* under the Apache Software License version 2.0 and the following code is used
* pursuant to that license.
*
* @author Luke Taylor
* @author Ben Alex
* @since 1.1.1
*/
public abstract class HexUtils {
public static String toHex(final byte[] bytes) {
return new String(encode(bytes));
}
private static final char[] HEX = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
public static char[] encode(final byte[] bytes) {
final int nBytes = bytes.length;
char[] result = new char[2 * nBytes];
int j = 0;
for (int i = 0; i < nBytes; i++) {
// Char for top 4 bits
result[j++] = HEX[(0xF0 & bytes[i]) >>> 4];
// Bottom 4
result[j++] = HEX[(0x0F & bytes[i])];
}
return result;
}
}

View File

@@ -0,0 +1,58 @@
package org.springframework.roo.support.util;
import java.io.Closeable;
import java.io.IOException;
import java.util.zip.ZipFile;
/**
* Static helper methods relating to I/O. Inspired by the eponymous class in
* Apache Commons I/O.
*
* @author Andrew Swan
* @since 1.2.0
*/
public final class IOUtils {
/**
* Quietly closes each of the given {@link Closeable}s, i.e. eats any
* {@link IOException}s arising.
*
* @param closeables the closeables to close (any of which can be
* <code>null</code> or already closed)
*/
public static void closeQuietly(final Closeable... closeables) {
for (final Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
// Ignore
}
}
}
}
/**
* Quietly closes each of the given {@link ZipFile}s, i.e. eats any
* {@link IOException}s arising.
*
* @param zipFiles the zipFiles to close (any of which can be
* <code>null</code> or already closed)
*/
public static void closeQuietly(final ZipFile... zipFiles) {
for (final ZipFile zipFile : zipFiles) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException e) {
// Ignore
}
}
}
}
/**
* Constructor is private to prevent instantiation
*/
private IOUtils() {}
}

View File

@@ -0,0 +1,16 @@
package org.springframework.roo.support.util;
/**
* A class which contains a number of number manipulation operations
*
* @author James Tyrrell
* @since 1.2.0
*/
public class MathUtils {
public static double round(final double valueToRound, final int numberOfDecimalPlaces) {
double multiplicationFactor = Math.pow(10, numberOfDecimalPlaces);
double interestedInZeroDPs = valueToRound * multiplicationFactor;
return Math.round(interestedInZeroDPs) / multiplicationFactor;
}
}

View File

@@ -0,0 +1,67 @@
package org.springframework.roo.support.util;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Retrieves text files from the classloader and displays them on-screen.
*
* <p>
* Respects normal Roo conventions such as all resources should appear under the same
* package as the bundle itself etc.
*
* @author Ben Alex
* @since 1.1.1
*/
public abstract class MessageDisplayUtils {
// Constants
private static Logger LOGGER = HandlerUtils.getLogger(MessageDisplayUtils.class);
/**
* Displays the requested file via the LOGGER API.
*
* <p>
* Each file must available from the classloader of the "owner". It must also be in the same
* package as the class of the "owner". So if the owner is com.foo.Bar, and the file is called
* "hello.txt", the file must appear in the same bundle as com.foo.Bar and be available from
* the resource path "/com/foo/Hello.txt".
*
* @param fileName the simple filename (required)
* @param owner the class which owns the file (required)
* @param important if true, it will display with a higher importance color where possible
*/
public static void displayFile(final String fileName, final Class<?> owner, final boolean important) {
Level level = important ? Level.SEVERE : Level.FINE;
String owningPackage = owner.getPackage().getName().replace('.', '/');
String fullResourceName = "/" + owningPackage + "/" + fileName;
InputStream inputStream = owner.getClassLoader().getResourceAsStream(fullResourceName);
if (inputStream == null) {
throw new IllegalStateException("Could not locate '" + fileName + "'");
}
try {
String message = FileCopyUtils.copyToString(new InputStreamReader(new BufferedInputStream(inputStream)));
LOGGER.log(level, message);
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
/**
* Same as {@link #displayFile(String, Class, boolean)} except it passes false as the
* final argument.
*
* @param fileName the simple filename (required)
* @param owner the class which owns the file (required)
*/
public static void displayFile(final String fileName, final Class<?> owner) {
displayFile(fileName, owner, false);
}
}

View File

@@ -0,0 +1,81 @@
package org.springframework.roo.support.util;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Provides extra functionality for Java Number classes.
*
* @author Alan Stewart
* @since 1.2.0
*/
public final class NumberUtils {
/**
* Returns the minimum value in the array.
*
* @param array an array of Numbers (can be <code>null</code>)
* @return the minimum value in the array, or null if all the elements are null
*/
public static BigDecimal min(final Number... array) {
return minOrMax(true, array);
}
/**
* Returns the maximum value in the array.
*
* @param array an array of Numbers (can be <code>null</code>)
* @return the maximum value in the array, or null if all the elements are null
*/
public static BigDecimal max(final Number... array) {
return minOrMax(false, array);
}
/**
* Finds the minimum or maxiumum value contained in the given array,
* ignoring any <code>null</code> elements
*
* @param findMinimum <code>false</code> to get the maximum
* @param numbers can be <code>null</code>, empty, or contain <code>null</code>
* elements
* @return <code>null</code> if the array is <code>null</code>, empty, or
* all its elements are <code>null</code>
*/
private static BigDecimal minOrMax(final boolean findMinimum, final Number... numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal extreme = null;
for (final Number number : numbers) {
if (number != null) {
final BigDecimal candidate = getBigDecimal(number);
if (extreme == null || (findMinimum ? candidate.compareTo(extreme) < 0 : candidate.compareTo(extreme) > 0)) {
// The non-null candidate is the new extreme
extreme = candidate;
}
}
}
return extreme;
}
/**
* Converts the given number to a {@link BigDecimal}
*
* @param number the number to convert (can be <code>null</code>)
* @return <code>null</code> if the given number was <code>null</code>
*/
private static BigDecimal getBigDecimal(final Number number) {
if (number == null || number instanceof BigDecimal) {
return (BigDecimal) number;
}
if (number instanceof BigInteger) {
return new BigDecimal((BigInteger) number);
}
return new BigDecimal(number.toString());
}
/**
* Constructor is private to prevent instantiation
*/
private NumberUtils() {}
}

View File

@@ -0,0 +1,925 @@
package org.springframework.roo.support.util;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Miscellaneous object utility methods. Mainly for internal use within the
* framework; consider Jakarta's Commons Lang for a more comprehensive suite
* of object utilities.
*
* @author Juergen Hoeller
* @author Keith Donald
* @author Rod Johnson
* @author Rob Harrop
* @author Alex Ruiz
* @see org.apache.commons.lang.ObjectUtils
*/
public final class ObjectUtils {
// Constants
private static final int INITIAL_HASH = 7;
private static final int MULTIPLIER = 31;
private static final String EMPTY_STRING = "";
private static final String NULL_STRING = "null";
private static final String ARRAY_START = "{";
private static final String ARRAY_END = "}";
private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
/**
* Return whether the given throwable is a checked exception:
* that is, neither a RuntimeException nor an Error.
*
* @param ex the throwable to check
* @return whether the throwable is a checked exception
* @see java.lang.Exception
* @see java.lang.RuntimeException
* @see java.lang.Error
*/
public static boolean isCheckedException(final Throwable ex) {
return !(ex instanceof RuntimeException || ex instanceof Error);
}
/**
* Check whether the given exception is compatible with the exceptions
* declared in a throws clause.
*
* @param ex the exception to checked
* @param declaredExceptions the exceptions declared in the throws clause
* @return whether the given exception is compatible
*/
public static boolean isCompatibleWithThrowsClause(final Throwable ex, final Class<?>... declaredExceptions) {
if (!isCheckedException(ex)) {
return true;
}
if (declaredExceptions != null) {
int i = 0;
while (i < declaredExceptions.length) {
if (declaredExceptions[i].isAssignableFrom(ex.getClass())) {
return true;
}
i++;
}
}
return false;
}
/**
* Return whether the given array is empty: that is, <code>null</code>
* or of zero length.
*
* @param array the array to check
* @return whether the given array is empty
*/
public static boolean isEmpty(final Object[] array) {
return array == null || array.length == 0;
}
/**
* Check whether the given array contains the given element.
*
* @param array the array to check (may be <code>null</code>,
* in which case the return value will always be <code>false</code>)
* @param element the element to check for
* @return whether the element has been found in the given array
*/
public static boolean containsElement(final Object[] array, final Object element) {
if (array == null) {
return false;
}
for (Object arrayEle : array) {
if (nullSafeEquals(arrayEle, element)) {
return true;
}
}
return false;
}
/**
* Append the given Object to the given array, returning a new array
* consisting of the input array contents plus the given Object.
*
* @param array the array to append to (can be <code>null</code>)
* @param obj the Object to append
* @return the new array (of the same component type; never <code>null</code>)
*/
public static Object[] addObjectToArray(final Object[] array, final Object obj) {
Class<?> compType = Object.class;
if (array != null) {
compType = array.getClass().getComponentType();
}
else if (obj != null) {
compType = obj.getClass();
}
int newArrLength = (array != null ? array.length + 1 : 1);
Object[] newArr = (Object[]) Array.newInstance(compType, newArrLength);
if (array != null) {
System.arraycopy(array, 0, newArr, 0, array.length);
}
newArr[newArr.length - 1] = obj;
return newArr;
}
/**
* Convert the given array (which may be a primitive array) to an
* object array (if necessary of primitive wrapper objects).
* <p>A <code>null</code> source value will be converted to an
* empty Object array.
*
* @param source the (potentially primitive) array
* @return the corresponding object array (never <code>null</code>)
* @throws IllegalArgumentException if the parameter is not an array
*/
public static Object[] toObjectArray(final Object source) {
if (source instanceof Object[]) {
return (Object[]) source;
}
if (source == null) {
return new Object[0];
}
if (!source.getClass().isArray()) {
throw new IllegalArgumentException("Source is not an array: " + source);
}
int length = Array.getLength(source);
if (length == 0) {
return new Object[0];
}
Class<?> wrapperType = Array.get(source, 0).getClass();
Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
for (int i = 0; i < length; i++) {
newArray[i] = Array.get(source, i);
}
return newArray;
}
//---------------------------------------------------------------------
// Convenience methods for content-based equality/hash-code handling
//---------------------------------------------------------------------
/**
* Determine if the given objects are equal, returning <code>true</code>
* if both are <code>null</code> or <code>false</code> if only one is
* <code>null</code>.
* <p>Compares arrays with <code>Arrays.equals</code>, performing an equality
* check based on the array elements rather than the array reference.
*
* @param o1 first Object to compare
* @param o2 second Object to compare
* @return whether the given objects are equal
* @see java.util.Arrays#equals
*/
public static boolean nullSafeEquals(final Object o1, final Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (o1.equals(o2)) {
return true;
}
if (o1.getClass().isArray() && o2.getClass().isArray()) {
if (o1 instanceof Object[] && o2 instanceof Object[]) {
return Arrays.equals((Object[]) o1, (Object[]) o2);
}
if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
return Arrays.equals((boolean[]) o1, (boolean[]) o2);
}
if (o1 instanceof byte[] && o2 instanceof byte[]) {
return Arrays.equals((byte[]) o1, (byte[]) o2);
}
if (o1 instanceof char[] && o2 instanceof char[]) {
return Arrays.equals((char[]) o1, (char[]) o2);
}
if (o1 instanceof double[] && o2 instanceof double[]) {
return Arrays.equals((double[]) o1, (double[]) o2);
}
if (o1 instanceof float[] && o2 instanceof float[]) {
return Arrays.equals((float[]) o1, (float[]) o2);
}
if (o1 instanceof int[] && o2 instanceof int[]) {
return Arrays.equals((int[]) o1, (int[]) o2);
}
if (o1 instanceof long[] && o2 instanceof long[]) {
return Arrays.equals((long[]) o1, (long[]) o2);
}
if (o1 instanceof short[] && o2 instanceof short[]) {
return Arrays.equals((short[]) o1, (short[]) o2);
}
}
return false;
}
/**
* Return as hash code for the given object; typically the value of
* <code>{@link Object#hashCode()}</code>. If the object is an array,
* this method will delegate to any of the <code>nullSafeHashCode</code>
* methods for arrays in this class. If the object is <code>null</code>,
* this method returns 0.
*
* @see #nullSafeHashCode(Object[])
* @see #nullSafeHashCode(boolean[])
* @see #nullSafeHashCode(byte[])
* @see #nullSafeHashCode(char[])
* @see #nullSafeHashCode(double[])
* @see #nullSafeHashCode(float[])
* @see #nullSafeHashCode(int[])
* @see #nullSafeHashCode(long[])
* @see #nullSafeHashCode(short[])
*/
public static int nullSafeHashCode(final Object obj) {
if (obj == null) {
return 0;
}
if (obj.getClass().isArray()) {
if (obj instanceof Object[]) {
return nullSafeHashCode((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeHashCode((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeHashCode((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeHashCode((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeHashCode((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeHashCode((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeHashCode((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeHashCode((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeHashCode((short[]) obj);
}
}
return obj.hashCode();
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array the array from whose elements to calculate the hash code (can be <code>null</code>)
* @return 0 if the array is <code>null</code>
*/
public static int nullSafeHashCode(final Object... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (final Object element : array) {
hash = MULTIPLIER * hash + nullSafeHashCode(element);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final boolean... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final byte... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final char... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final double... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final float... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final int... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final long... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + hashCode(array[i]);
}
return hash;
}
/**
* Return a hash code based on the contents of the specified array.
*
* @param array can be <code>null</code>
* @return 0 if <code>array</code> is <code>null</code>
*/
public static int nullSafeHashCode(final short... array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
int arraySize = array.length;
for (int i = 0; i < arraySize; i++) {
hash = MULTIPLIER * hash + array[i];
}
return hash;
}
/**
* Returns the hash code of the given boolean value.
*
* @param bool the boolean for which to return the hash code
* @return see {@link Boolean#hashCode()}
*/
public static int hashCode(final boolean bool) {
return Boolean.valueOf(bool).hashCode();
}
/**
* Return the same value as <code>{@link Double#hashCode()}</code>.
*
* @see Double#hashCode()
*/
public static int hashCode(final double dbl) {
long bits = Double.doubleToLongBits(dbl);
return hashCode(bits);
}
/**
* Return the same value as <code>{@link Float#hashCode()}</code>.
*
* @see Float#hashCode()
*/
public static int hashCode(final float flt) {
return Float.floatToIntBits(flt);
}
/**
* Return the same value as <code>{@link Long#hashCode()}</code>.
*
* @see Long#hashCode()
*/
public static int hashCode(final long lng) {
return (int) (lng ^ (lng >>> 32));
}
//---------------------------------------------------------------------
// Convenience methods for toString output
//---------------------------------------------------------------------
/**
* Return a String representation of an object's overall identity.
*
* @param obj the object (may be <code>null</code>)
* @return the object's identity as String representation,
* or an empty String if the object was <code>null</code>
*/
public static String identityToString(final Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return obj.getClass().getName() + "@" + getIdentityHexString(obj);
}
/**
* Return a hex String form of an object's identity hash code.
*
* @param obj the object
* @return the object's identity code in hex notation
*/
public static String getIdentityHexString(final Object obj) {
return Integer.toHexString(System.identityHashCode(obj));
}
/**
* Return a content-based String representation if <code>obj</code> is
* not <code>null</code>; otherwise returns an empty String.
* <p>Differs from {@link #nullSafeToString(Object)} in that it returns
* an empty String rather than "null" for a <code>null</code> value.
*
* @param obj the object to build a display String for
* @return a display String representation of <code>obj</code>
* @see #nullSafeToString(Object)
*/
public static String getDisplayString(final Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return nullSafeToString(obj);
}
/**
* Determine the class name for the given object.
* <p>Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.
*
* @param obj the object to introspect (may be <code>null</code>)
* @return the corresponding class name
*/
public static String nullSafeClassName(final Object obj) {
return (obj != null ? obj.getClass().getName() : NULL_STRING);
}
/**
* Return a String representation of the specified Object.
* <p>Builds a String representation of the contents in case of an array.
* Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.
*
* @param obj the object to build a String representation for
* @return a String representation of <code>obj</code>
*/
public static String nullSafeToString(final Object obj) {
if (obj == null) {
return NULL_STRING;
}
if (obj instanceof String) {
return (String) obj;
}
if (obj instanceof Object[]) {
return nullSafeToString((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeToString((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeToString((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeToString((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeToString((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeToString((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeToString((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeToString((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeToString((short[]) obj);
}
String str = obj.toString();
return (str != null ? str : EMPTY_STRING);
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final Object... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(String.valueOf(array[i]));
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final boolean... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final byte... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final char... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append("'").append(array[i]).append("'");
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final double... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final float... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final int... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final long... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Return a String representation of the contents of the specified array.
* <p>The String representation consists of a list of the array's elements,
* enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated
* by the characters <code>", "</code> (a comma followed by a space). Returns
* <code>"null"</code> if <code>array</code> is <code>null</code>.
*
* @param array the array to build a String representation for
* @return a String representation of <code>array</code>
*/
public static String nullSafeToString(final short... array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
* Compares the two given objects, with <code>null</code> equivalent to
* <code>null</code> and <code>null</code> "less than" any
* non-<code>null</code> instance. Two non-<code>null</code> instances are
* compared using the first one's {@link Comparable#compareTo(Object)}
* method.
*
* @param <T> the type of objects being compared
* @param one the first object being compared (can be <code>null</code>)
* @param other the second object being compared (can be <code>null</code>)
* @return see {@link Comparable#compareTo(Object)}
* @since 1.2.0
*/
public static <T> int nullSafeComparison(final Comparable<T> one, final T other) {
if (one == null) {
if (other == null) {
return 0;
}
// First is null, second is not
return -1;
}
// If we get here, the first object is non-null
if (other == null) {
return 1;
}
return one.compareTo(other);
}
/**
* Returns the String representation of the given object, or if it's
* <code>null</code>, the given default string
*
* @param object the object to represent as a String (can be <code>null</code>)
* @param defaultValue the value to return if the given object is <code>null</code> (can itself be blank)
* @return see above
* @since 1.2.0
*/
public static String toString(final Object object, final String defaultValue) {
if (object == null) {
return defaultValue;
}
return object.toString();
}
/**
* Returns the given object if not <code>null</code>, otherwise the given
* default value
*
* @param <T> the type of object being defaulted
* @param object the object to check (can be <code>null</code>)
* @param defaultValue the default value (can be <code>null</code>)
* @return <code>null</code> iff both values are <code>null</code>
* @since 1.2.0
*/
public static <T> T defaultIfNull(final T object, final T defaultValue) {
if (object == null) {
return defaultValue;
}
return object;
}
/**
* Constructor is private to prevent instantiation
*/
private ObjectUtils() {}
}

View File

@@ -0,0 +1,15 @@
package org.springframework.roo.support.util;
/**
* Utilities for handling OS-specific behavior.
*
* @author Joris Kuipers
* @since 1.1.1
*/
public class OsUtils {
private static final boolean WINDOWS_OS = System.getProperty("os.name").toLowerCase().contains("windows");
public static boolean isWindows() {
return WINDOWS_OS;
}
}

View File

@@ -0,0 +1,70 @@
package org.springframework.roo.support.util;
/**
* A pair with a key of type "K" and a value of type "V". Instances are immutable.
*
* @author Andrew Swan
* @since 1.2.0
* @param <K> the key type
* @param <V> the value type
*/
public class Pair<K, V> {
// Fields
private final K key;
private final V value;
/**
* Constructor
*
* @param key can be <code>null</code>
* @param value can be <code>null</code>
*/
public Pair(final K key, final V value) {
this.key = key;
this.value = value;
}
/**
* Returns the key
*
* @return <code>null</code> if it is
*/
public K getKey() {
return key;
}
/**
* Returns the value
*
* @return <code>null</code> if it is
*/
public V getValue() {
return value;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Pair)) {
return false;
}
final Pair<?, ?> otherPair = (Pair<?, ?>) obj;
return ObjectUtils.nullSafeEquals(key, otherPair.getKey()) && ObjectUtils.nullSafeEquals(value, otherPair.getValue());
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(new Object[] {getKey(), getValue()});
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("key: ").append(key);
sb.append(", value: ").append(value);
return sb.toString();
}
}

View File

@@ -0,0 +1,103 @@
package org.springframework.roo.support.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A {@link List} of {@link Pair}s. Unlike a {@link java.util.Map}, it can have
* duplicate and/or <code>null</code> keys.
*
* @author Andrew Swan
* @since 1.2.0
* @param <K> the type of key
* @param <V> the type of value
*/
public class PairList<K, V> extends ArrayList<Pair<K, V>> {
// For serialisation
private static final long serialVersionUID = 5990417235907246300L;
/**
* Returns the given array of pairs as a modifiable list
*
* @param <K> the type of key
* @param <V> the type of value
* @param pairs the pairs to put in a list
* @return a non-<code>null</code> list
*/
public PairList(final Pair<K, V>... pairs) {
addAll(Arrays.asList(pairs));
}
/**
* Constructor for building a list of the given key-value pairs
*
* @param keys the keys (can be null)
* @param values the values (must be null if the keys are null, otherwise
* must be non-null and of the same size as the keys)
*/
public PairList(final List<? extends K> keys, final List<? extends V> values) {
Assert.isTrue(!(keys == null ^ values == null), "Parameter types and names must either both be null or both be not null");
if (keys == null) {
Assert.isTrue(values == null, "Parameter names must be null if types are null");
}
else {
Assert.isTrue(values != null, "Parameter names are required if types are provided");
Assert.isTrue(keys.size() == values.size(), "Expected " + keys.size() + " values but found " + values.size());
for (int i = 0; i < keys.size(); i++) {
add(keys.get(i), values.get(i));
}
}
}
/**
* Constructor for an empty list of pairs
*/
public PairList() {
// Empty
}
/**
* Returns the keys of each {@link Pair} in this list
*
* @return a non-<code>null</code> list
*/
public List<K> getKeys() {
final List<K> keys = new ArrayList<K>();
for (final Pair<K, ?> pair : this) {
keys.add(pair.getKey());
}
return keys;
}
/**
* Returns the values of each {@link Pair} in this list
*
* @return a non-<code>null</code> modifiable copy of this list
*/
public List<V> getValues() {
final List<V> values = new ArrayList<V>();
for (final Pair<?, V> pair : this) {
values.add(pair.getValue());
}
return values;
}
/**
* Adds a new pair to this list with the given key and value
*
* @param key the key to add; can be <code>null</code>
* @param value the value to add; can be <code>null</code>
* @return true (as specified by Collection.add(E))
*/
public boolean add(final K key, final V value) {
return add(new Pair<K, V>(key, value));
}
@SuppressWarnings("unchecked")
@Override
public Pair<K, V>[] toArray() {
return super.toArray(new Pair[size()]);
}
}

View File

@@ -0,0 +1,604 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* 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.springframework.roo.support.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Simple utility class for working with the reflection API and handling
* reflection exceptions.
*
* <p>Only intended for internal use.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Rod Johnson
* @author Costin Leau
* @author Sam Brannen
* @since 1.2.2
*/
public abstract class ReflectionUtils {
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with
* the supplied <code>name</code>. Searches all superclasses up to {@link Object}.
* @param clazz the class to introspect
* @param name the name of the field
* @return the corresponding Field object, or <code>null</code> if not found
*/
public static Field findField(final Class<?> clazz, final String name) {
return findField(clazz, name, null);
}
/**
* Attempt to find a {@link Field field} on the supplied {@link Class} with
* the supplied <code>name</code> and/or {@link Class type}. Searches all
* superclasses up to {@link Object}.
* @param clazz the class to introspect
* @param name the name of the field (may be <code>null</code> if type is specified)
* @param type the type of the field (may be <code>null</code> if name is specified)
* @return the corresponding Field object, or <code>null</code> if not found
*/
public static Field findField(final Class<?> clazz, final String name, final Class<?> type) {
Assert.notNull(clazz, "Class must not be null");
Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
Class<?> searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
Field[] fields = searchType.getDeclaredFields();
for (Field field : fields) {
if ((name == null || name.equals(field.getName())) &&
(type == null || type.equals(field.getType()))) {
return field;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Set the field represented by the supplied {@link Field field object} on
* the specified {@link Object target object} to the specified
* <code>value</code>. In accordance with
* {@link Field#set(Object, Object)} semantics, the new value is
* automatically unwrapped if the underlying field has a primitive type.
* <p>Thrown exceptions are handled via a call to
* {@link #handleReflectionException(Exception)}.
* @param field the field to set
* @param target the target object on which to set the field
* @param value the value to set; may be <code>null</code>
*/
public static void setField(final Field field, final Object target, final Object value) {
try {
field.set(target, value);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException(
"Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Get the field represented by the supplied {@link Field field object} on
* the specified {@link Object target object}. In accordance with
* {@link Field#get(Object)} semantics, the returned value is
* automatically wrapped if the underlying field has a primitive type.
* <p>Thrown exceptions are handled via a call to
* {@link #handleReflectionException(Exception)}.
* @param field the field to get
* @param target the target object from which to get the field
* @return the field's current value
*/
public static Object getField(final Field field, final Object target) {
try {
return field.get(target);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
throw new IllegalStateException(
"Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
}
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and no parameters. Searches all superclasses up to <code>Object</code>.
* <p>Returns <code>null</code> if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @return the Method object, or <code>null</code> if none found
*/
public static Method findMethod(final Class<?> clazz, final String name) {
return findMethod(clazz, name, new Class[0]);
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and parameter types. Searches all superclasses up to <code>Object</code>.
* <p>Returns <code>null</code> if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @param parameterTypes the parameter types of the method
* (may be <code>null</code> to indicate any signature)
* @return the Method object, or <code>null</code> if none found
*/
public static Method findMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(name, "Method name must not be null");
Class<?> searchType = clazz;
while (!Object.class.equals(searchType) && searchType != null) {
Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
for (Method method : methods) {
if (name.equals(method.getName()) &&
(parameterTypes == null || Arrays.equals(parameterTypes, method.getParameterTypes()))) {
return method;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
/**
* Invoke the specified {@link Method} against the supplied target object
* with no arguments. The target object can be <code>null</code> when
* invoking a static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeMethod(final Method method, final Object target) {
return invokeMethod(method, target, null);
}
/**
* Invoke the specified {@link Method} against the supplied target object
* with the supplied arguments. The target object can be <code>null</code>
* when invoking a static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be <code>null</code>)
* @return the invocation result, if any
*/
public static Object invokeMethod(final Method method, final Object target, final Object[] args) {
try {
return method.invoke(target, args);
}
catch (Exception ex) {
handleReflectionException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied
* target object with no arguments.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(final Method method, final Object target) throws SQLException {
return invokeJdbcMethod(method, target, null);
}
/**
* Invoke the specified JDBC API {@link Method} against the supplied
* target object with the supplied arguments.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be <code>null</code>)
* @return the invocation result, if any
* @throws SQLException the JDBC API SQLException to rethrow (if any)
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeJdbcMethod(final Method method, final Object target, final Object[] args) throws SQLException {
try {
return method.invoke(target, args);
}
catch (IllegalAccessException ex) {
handleReflectionException(ex);
}
catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof SQLException) {
throw (SQLException) ex.getTargetException();
}
handleInvocationTargetException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Handle the given reflection exception. Should only be called if
* no checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of an
* InvocationTargetException with such a root cause. Throws an
* IllegalStateException with an appropriate message else.
* @param ex the reflection exception to handle
*/
public static void handleReflectionException(final Exception ex) {
if (ex instanceof NoSuchMethodException) {
throw new IllegalStateException("Method not found: " + ex.getMessage());
}
if (ex instanceof IllegalAccessException) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
if (ex instanceof InvocationTargetException) {
handleInvocationTargetException((InvocationTargetException) ex);
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
handleUnexpectedException(ex);
}
/**
* Handle the given invocation target exception. Should only be called if
* no checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of such
* a root cause. Throws an IllegalStateException else.
* @param ex the invocation target exception to handle
*/
public static void handleInvocationTargetException(final InvocationTargetException ex) {
rethrowRuntimeException(ex.getTargetException());
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}.
* Should only be called if no checked exception is expected to be thrown by
* the target method.
* <p>Rethrows the underlying exception cast to an {@link RuntimeException}
* or {@link Error} if appropriate; otherwise, throws an
* {@link IllegalStateException}.
* @param ex the exception to rethrow
* @throws RuntimeException the rethrown exception
*/
public static void rethrowRuntimeException(final Throwable ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
handleUnexpectedException(ex);
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}.
* Should only be called if no checked exception is expected to be thrown by
* the target method.
* <p>Rethrows the underlying exception cast to an {@link Exception} or
* {@link Error} if appropriate; otherwise, throws an
* {@link IllegalStateException}.
* @param ex the exception to rethrow
* @throws Exception the rethrown exception (in case of a checked exception)
*/
public static void rethrowException(final Throwable ex) throws Exception {
if (ex instanceof Exception) {
throw (Exception) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
handleUnexpectedException(ex);
}
/**
* Throws an IllegalStateException with the given exception as root cause.
* @param ex the unexpected exception
*/
private static void handleUnexpectedException(final Throwable ex) {
throw new IllegalStateException("Unexpected exception thrown", ex);
}
/**
* Determine whether the given method explicitly declares the given exception
* or one of its superclasses, which means that an exception of that type
* can be propagated as-is within a reflective invocation.
* @param method the declaring method
* @param exceptionType the exception to throw
* @return <code>true</code> if the exception can be thrown as-is;
* <code>false</code> if it needs to be wrapped
*/
public static boolean declaresException(final Method method, final Class<?> exceptionType) {
Assert.notNull(method, "Method must not be null");
Class<?>[] declaredExceptions = method.getExceptionTypes();
for (Class<?> declaredException : declaredExceptions) {
if (declaredException.isAssignableFrom(exceptionType)) {
return true;
}
}
return false;
}
/**
* Determine whether the given field is a "public static final" constant.
* @param field the field to check
*/
public static boolean isPublicStaticFinal(final Field field) {
int modifiers = field.getModifiers();
return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
}
/**
* Determine whether the given method is an "equals" method.
* @see java.lang.Object#equals
*/
public static boolean isEqualsMethod(final Method method) {
if (method == null || !method.getName().equals("equals")) {
return false;
}
Class<?>[] parameterTypes = method.getParameterTypes();
return (parameterTypes.length == 1 && parameterTypes[0] == Object.class);
}
/**
* Determine whether the given method is a "hashCode" method.
* @see java.lang.Object#hashCode
*/
public static boolean isHashCodeMethod(final Method method) {
return (method != null && method.getName().equals("hashCode") &&
method.getParameterTypes().length == 0);
}
/**
* Determine whether the given method is a "toString" method.
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(final Method method) {
return (method != null && method.getName().equals("toString") &&
method.getParameterTypes().length == 0);
}
/**
* Make the given field accessible, explicitly setting it accessible if necessary.
* The <code>setAccessible(true)</code> method is only called when actually necessary,
* to avoid unnecessary conflicts with a JVM SecurityManager (if active).
* @param field the field to make accessible
* @see java.lang.reflect.Field#setAccessible
*/
public static void makeAccessible(final Field field) {
if (!Modifier.isPublic(field.getModifiers()) ||
!Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
field.setAccessible(true);
}
}
/**
* Make the given method accessible, explicitly setting it accessible if necessary.
* The <code>setAccessible(true)</code> method is only called when actually necessary,
* to avoid unnecessary conflicts with a JVM SecurityManager (if active).
* @param method the method to make accessible
* @see java.lang.reflect.Method#setAccessible
*/
public static void makeAccessible(final Method method) {
if (!Modifier.isPublic(method.getModifiers()) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
method.setAccessible(true);
}
}
/**
* Make the given constructor accessible, explicitly setting it accessible if necessary.
* The <code>setAccessible(true)</code> method is only called when actually necessary,
* to avoid unnecessary conflicts with a JVM SecurityManager (if active).
* @param ctor the constructor to make accessible
* @see java.lang.reflect.Constructor#setAccessible
*/
public static void makeAccessible(final Constructor<?> ctor) {
if (!Modifier.isPublic(ctor.getModifiers()) ||
!Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) {
ctor.setAccessible(true);
}
}
/**
* Perform the given callback operation on all matching methods of the
* given class and superclasses.
* <p>The same named method occurring on subclass and superclass will
* appear twice, unless excluded by a {@link MethodFilter}.
* @param targetClass class to start looking at
* @param mc the callback to invoke for each method
* @see #doWithMethods(Class, MethodCallback, MethodFilter)
*/
public static void doWithMethods(final Class<?> targetClass, final MethodCallback mc) throws IllegalArgumentException {
doWithMethods(targetClass, mc, null);
}
/**
* Perform the given callback operation on all matching methods of the
* given class and superclasses.
* <p>The same named method occurring on subclass and superclass will
* appear twice, unless excluded by the specified {@link MethodFilter}.
* @param targetClass class to start looking at
* @param mc the callback to invoke for each method
* @param mf the filter that determines the methods to apply the callback to
*/
public static void doWithMethods(Class<?> targetClass, final MethodCallback mc, final MethodFilter mf) throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
do {
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException(
"Shouldn't be illegal to access method '" + method.getName() + "': " + ex);
}
}
targetClass = targetClass.getSuperclass();
}
while (targetClass != null);
}
/**
* Get all declared methods on the leaf class and all superclasses.
* Leaf class methods are included first.
*/
public static Method[] getAllDeclaredMethods(final Class<?> leafClass) throws IllegalArgumentException {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
public void doWith(final Method method) {
methods.add(method);
}
});
return methods.toArray(new Method[methods.size()]);
}
/**
* Invoke the given callback on all fields in the target class,
* going up the class hierarchy to get all declared fields.
* @param targetClass the target class to analyze
* @param fc the callback to invoke for each field
*/
public static void doWithFields(final Class<?> targetClass, final FieldCallback fc) throws IllegalArgumentException {
doWithFields(targetClass, fc, null);
}
/**
* Invoke the given callback on all fields in the target class,
* going up the class hierarchy to get all declared fields.
* @param targetClass the target class to analyze
* @param fc the callback to invoke for each field
* @param ff the filter that determines the fields to apply the callback to
*/
public static void doWithFields(Class<?> targetClass, final FieldCallback fc, final FieldFilter ff) throws IllegalArgumentException {
// Keep backing up the inheritance hierarchy.
do {
// Copy each field declared on this class unless it's static or file.
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
// Skip static and final fields.
if (ff != null && !ff.matches(field)) {
continue;
}
try {
fc.doWith(field);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException(
"Shouldn't be illegal to access field '" + field.getName() + "': " + ex);
}
}
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
}
/**
* Given the source object and the destination, which must be the same class
* or a subclass, copy all fields, including inherited fields. Designed to
* work on objects with public no-arg constructors.
* @throws IllegalArgumentException if the arguments are incompatible
*/
public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException {
if (src == null) {
throw new IllegalArgumentException("Source for field copy cannot be null");
}
if (dest == null) {
throw new IllegalArgumentException("Destination for field copy cannot be null");
}
if (!src.getClass().isAssignableFrom(dest.getClass())) {
throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
"] must be same or subclass as source class [" + src.getClass().getName() + "]");
}
doWithFields(src.getClass(), new FieldCallback() {
public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
makeAccessible(field);
Object srcValue = field.get(src);
field.set(dest, srcValue);
}
}, COPYABLE_FIELDS);
}
/**
* Action to take on each method.
*/
public static interface MethodCallback {
/**
* Perform an operation using the given method.
* @param method the method to operate on
*/
void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to method fields to be operated on by a method callback.
*/
public static interface MethodFilter {
/**
* Determine whether the given method matches.
* @param method the method to check
*/
boolean matches(Method method);
}
/**
* Callback interface invoked on each field in the hierarchy.
*/
public static interface FieldCallback {
/**
* Perform an operation using the given field.
* @param field the field to operate on
*/
void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
}
/**
* Callback optionally used to filter fields to be operated on by a field callback.
*/
public static interface FieldFilter {
/**
* Determine whether the given field matches.
* @param field the field to check
*/
boolean matches(Field field);
}
/**
* Pre-built FieldFilter that matches all non-static, non-final fields.
*/
public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
public boolean matches(final Field field) {
return !(Modifier.isStatic(field.getModifiers()) ||
Modifier.isFinal(field.getModifiers()));
}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
package org.springframework.roo.support.util;
import java.io.InputStream;
/**
* Utilities for dealing with "templates", which are commonly used by ROO add-ons.
*
* @author Ben Alex
* @since 1.0
*/
public final class TemplateUtils {
/**
* Determines the path to the requested template.
*
* @param clazz which owns the template (required)
* @param templateFilename the filename of the template (required)
* @return the full classloader-specific path to the template (never null)
* @deprecated use {@link FileUtils#getPath(Class, String)} instead
*/
@Deprecated
public static String getTemplatePath(final Class<?> loadingClass, final String relativeFilename) {
return FileUtils.getPath(loadingClass, relativeFilename);
}
/**
* Acquires an {@link InputStream} to the requested classloader-derived template.
*
* @param clazz which owns the template (required)
* @param templateFilename the filename of the template (required)
* @return the input stream (never null; an exception is thrown if cannot be found)
* @deprecated use {@link FileUtils#getInputStream(Class, String)} instead
*/
@Deprecated
public static InputStream getTemplate(final Class<?> clazz, final String templateFilename) {
return clazz.getResourceAsStream(templateFilename);
}
/**
* Constructor is private to prevent instantiation
*/
private TemplateUtils() {}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.roo.support.util;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
/**
* Utility methods relating to networking types such as URLs.
*
* @author Andrew Swan
* @since 1.2.0
*/
public final class UrlUtils {
/**
* Converts the given URI to a URL; equivalent to {@link URI#toURL()}
* except that it throws a runtime exception.
*
* @param uri
* @return a non-<code>null</code> URL
* @throws IllegalArgumentException if the conversion is not possible
*/
public static URL toURL(final URI uri) {
try {
return uri.toURL();
} catch (final MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Constructor is private to prevent instantiation
*/
private UrlUtils() {}
}

View File

@@ -0,0 +1,587 @@
package org.springframework.roo.support.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Helper util class to allow more convenient handling of web.xml file in Web projects.
*
* @author Stefan Schmidt
* @since 1.1
*/
public final class WebXmlUtils {
// Constants
private static final String WEB_APP_XPATH = "/web-app/";
private static final String WHITESPACE = "[ \t\r\n]";
/**
* Set the display-name element in the web.xml document.
*
* @param displayName (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void setDisplayName(final String displayName, final Document document, final String comment) {
Assert.hasText(displayName, "display name required");
Assert.notNull(document, "Web XML document required");
Element displayNameElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "display-name", document.getDocumentElement());
if (displayNameElement == null) {
displayNameElement = document.createElement("display-name");
insertBetween(displayNameElement, "the-start", "description", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(displayNameElement, comment, document);
}
}
displayNameElement.setTextContent(displayName);
}
/**
* Set the description element in the web.xml document.
*
* @param description (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void setDescription(final String description, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.hasText(description, "Description required");
Element descriptionElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "description", document.getDocumentElement());
if (descriptionElement == null) {
descriptionElement = document.createElement("description");
insertBetween(descriptionElement, "display-name[last()]", "context-param", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(descriptionElement, comment, document);
}
}
descriptionElement.setTextContent(description);
}
/**
* Add a context param to the web.xml document
*
* @param contextParam (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void addContextParam(final WebXmlParam contextParam, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.notNull(contextParam, "Context param required");
Element contextParamElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "context-param[param-name = '" + contextParam.getName() + "']", document.getDocumentElement());
if (contextParamElement == null) {
contextParamElement = new XmlElementBuilder("context-param", document).addChild(new XmlElementBuilder("param-name", document).setText(contextParam.getName()).build()).build();
insertBetween(contextParamElement, "description[last()]", "filter", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(contextParamElement, comment, document);
}
}
appendChildIfNotPresent(contextParamElement, new XmlElementBuilder("param-value", document).setText(contextParam.getValue()).build());
}
/**
* Add a new filter definition to web.xml document. The filter will be added AFTER (FilterPosition.LAST) all existing filters.
*
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams a vararg of initial parameters (optional)
*/
public static void addFilter(final String filterName, final String filterClass, final String urlPattern, final Document document, final String comment, final WebXmlParam... initParams) {
addFilterAtPosition(FilterPosition.LAST, null, null, filterName, filterClass, urlPattern, document, comment, initParams);
}
/**
* Add a new filter definition to web.xml document. The filter will be added at the FilterPosition specified.
*
* @param filterPosition Filter position (required)
* @param beforeFilterName (optional for filter position FIRST and LAST, required for BEFORE and AFTER)
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams (optional)
*/
public static void addFilterAtPosition(final FilterPosition filterPosition, final String afterFilterName, final String beforeFilterName, final String filterName, final String filterClass, final String urlPattern, final Document document, final String comment, final WebXmlParam... initParams) {
addFilterAtPosition(filterPosition, afterFilterName, beforeFilterName, filterName, filterClass, urlPattern, document, comment, initParams == null ? new ArrayList<WebXmlParam>() : Arrays.asList(initParams), new ArrayList<Dispatcher>());
}
/**
* Add a new filter definition to web.xml document. The filter will be added at the FilterPosition specified.
*
* @param filterPosition Filter position (required)
* @param beforeFilterName (optional for filter position FIRST and LAST, required for BEFORE and AFTER)
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams (optional)
* @param dispatchers (optional)
*/
public static void addFilterAtPosition(final FilterPosition filterPosition, final String afterFilterName, final String beforeFilterName, final String filterName, final String filterClass, final String urlPattern, final Document document, final String comment, List<WebXmlParam> initParams, final List<Dispatcher> dispatchers) {
Assert.notNull(document, "Web XML document required");
Assert.hasText(filterName, "Filter name required");
Assert.hasText(filterClass, "Filter class required");
Assert.notNull(urlPattern, "Filter URL mapping pattern required");
if (initParams == null) {
initParams = new ArrayList<WebXmlUtils.WebXmlParam>();
}
// Creating filter
Element filterElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "filter[filter-name = '" + filterName + "']", document.getDocumentElement());
if (filterElement == null) {
filterElement = new XmlElementBuilder("filter", document).addChild(new XmlElementBuilder("filter-name", document).setText(filterName).build()).build();
if (filterPosition.equals(FilterPosition.FIRST)) {
insertBetween(filterElement, "context-param", "filter", document);
} else if (filterPosition.equals(FilterPosition.BEFORE)) {
Assert.hasText(beforeFilterName, "The filter position filter name is required when using FilterPosition.BEFORE");
insertBefore(filterElement, "filter[filter-name = '" + beforeFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.AFTER)) {
Assert.hasText(afterFilterName, "The filter position filter name is required when using FilterPosition.AFTER");
insertAfter(filterElement, "filter[filter-name = '" + afterFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.BETWEEN)) {
Assert.hasText(beforeFilterName, "The 'before' filter name is required when using FilterPosition.BETWEEN");
Assert.hasText(afterFilterName, "The 'after' filter name is required when using FilterPosition.BETWEEN");
insertBetween(filterElement, "filter[filter-name = '" + afterFilterName + "']", "filter[filter-name = '" + beforeFilterName + "']", document);
} else {
insertBetween(filterElement, "context-param[last()]", "filter-mapping", document);
}
if (StringUtils.hasText(comment)) {
addCommentBefore(filterElement, comment, document);
}
}
appendChildIfNotPresent(filterElement, new XmlElementBuilder("filter-class", document).setText(filterClass).build());
for (final WebXmlParam initParam : initParams) {
appendChildIfNotPresent(filterElement, new XmlElementBuilder("init-param", document).addChild(new XmlElementBuilder("param-name", document).setText(initParam.getName()).build()).addChild(new XmlElementBuilder("param-value", document).setText(initParam.getValue()).build()).build());
}
// Creating filter mapping
Element filterMappingElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "filter-mapping[filter-name = '" + filterName + "']", document.getDocumentElement());
if (filterMappingElement == null) {
filterMappingElement = new XmlElementBuilder("filter-mapping", document).addChild(new XmlElementBuilder("filter-name", document).setText(filterName).build()).build();
if (filterPosition.equals(FilterPosition.FIRST)) {
insertBetween(filterMappingElement, "filter", "filter-mapping", document);
} else if (filterPosition.equals(FilterPosition.BEFORE)) {
insertBefore(filterMappingElement, "filter-mapping[filter-name = '" + beforeFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.AFTER)) {
insertAfter(filterMappingElement, "filter-mapping[filter-name = '" + beforeFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.BETWEEN)) {
insertBetween(filterMappingElement, "filter-mapping[filter-name = '" + afterFilterName + "']", "filter-mapping[filter-name = '" + beforeFilterName + "']", document);
} else {
insertBetween(filterMappingElement, "filter-mapping[last()]", "listener", document);
}
}
appendChildIfNotPresent(filterMappingElement, new XmlElementBuilder("url-pattern", document).setText(urlPattern).build());
for (final Dispatcher dispatcher : dispatchers) {
appendChildIfNotPresent(filterMappingElement, new XmlElementBuilder("dispatcher", document).setText(dispatcher.name()).build());
}
}
/**
* Add listener element to web.xml document
*
* @param className the fully qualified name of the listener type (required)
* @param document (required)
* @param comment (optional)
*/
public static void addListener(final String className, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.hasText(className, "Class name required");
Element listenerElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "listener[listener-class = '" + className + "']", document.getDocumentElement());
if (listenerElement == null) {
listenerElement = new XmlElementBuilder("listener", document).addChild(new XmlElementBuilder("listener-class", document).setText(className).build()).build();
insertBetween(listenerElement, "filter-mapping[last()]", "servlet", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(listenerElement, comment, document);
}
}
}
/**
* Add servlet element to the web.xml document
*
* @param servletName (required)
* @param className the fully qualified name of the servlet type (required)
* @param urlPattern this can be set to null in which case the servletName will be used for mapping (optional)
* @param loadOnStartup (optional)
* @param document (required)
* @param comment (optional)
* @param initParams (optional)
*/
public static void addServlet(final String servletName, final String className, final String urlPattern, final Integer loadOnStartup, final Document document, final String comment, final WebXmlParam... initParams) {
Assert.notNull(document, "Web XML document required");
Assert.hasText(servletName, "Servlet name required");
Assert.hasText(className, "Fully qualified class name required");
// Create servlet
Element servletElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "servlet[servlet-name = '" + servletName + "']", document.getDocumentElement());
if (servletElement == null) {
servletElement = new XmlElementBuilder("servlet", document).addChild(new XmlElementBuilder("servlet-name", document).setText(servletName).build()).build();
insertBetween(servletElement, "listener[last()]", "servlet-mapping", document);
if (comment != null && comment.length() > 0) {
addCommentBefore(servletElement, comment, document);
}
}
appendChildIfNotPresent(servletElement, new XmlElementBuilder("servlet-class", document).setText(className).build());
for (final WebXmlParam initParam : initParams) {
appendChildIfNotPresent(servletElement, new XmlElementBuilder("init-param", document).addChild(new XmlElementBuilder("param-name", document).setText(initParam.getName()).build()).addChild(new XmlElementBuilder("param-value", document).setText(initParam.getValue()).build()).build());
}
if (loadOnStartup != null) {
appendChildIfNotPresent(servletElement, new XmlElementBuilder("load-on-startup", document).setText(loadOnStartup.toString()).build());
}
// Create servlet mapping
Element servletMappingElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "servlet-mapping[servlet-name = '" + servletName + "']", document.getDocumentElement());
if (servletMappingElement == null) {
servletMappingElement = new XmlElementBuilder("servlet-mapping", document).addChild(new XmlElementBuilder("servlet-name", document).setText(servletName).build()).build();
insertBetween(servletMappingElement, "servlet[last()]", "session-config", document);
}
if (StringUtils.hasText(urlPattern)) {
appendChildIfNotPresent(servletMappingElement, new XmlElementBuilder("url-pattern", document).setText(urlPattern).build());
} else {
appendChildIfNotPresent(servletMappingElement, new XmlElementBuilder("servlet-name", document).setText(servletName).build());
}
}
/**
* Set session timeout in web.xml document
*
* @param timeout
* @param document (required)
* @param comment (optional)
*/
public static void setSessionTimeout(final int timeout, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.notNull(timeout, "Timeout required");
Element sessionConfigElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "session-config", document.getDocumentElement());
if (sessionConfigElement == null) {
sessionConfigElement = document.createElement("session-config");
insertBetween(sessionConfigElement, "servlet-mapping[last()]", "welcome-file-list", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(sessionConfigElement, comment, document);
}
}
appendChildIfNotPresent(sessionConfigElement, new XmlElementBuilder("session-timeout", document).setText(String.valueOf(timeout)).build());
}
/**
* Add a welcome file definition to web.xml document
*
* @param path (required)
* @param document (required)
* @param comment (optional)
*/
public static void addWelcomeFile(final String path, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.hasText("Path required");
Element welcomeFileElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "welcome-file-list", document.getDocumentElement());
if (welcomeFileElement == null) {
welcomeFileElement = document.createElement("welcome-file-list");
insertBetween(welcomeFileElement, "session-config[last()]", "error-page", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(welcomeFileElement, comment, document);
}
}
appendChildIfNotPresent(welcomeFileElement, new XmlElementBuilder("welcome-file", document).setText(path).build());
}
/**
* Add exception type to web.xml document
*
* @param exceptionType fully qualified exception type name (required)
* @param location (required)
* @param document (required)
* @param comment (optional)
*/
public static void addExceptionType(final String exceptionType, final String location, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.hasText(exceptionType, "Fully qualified exception type name required");
Assert.hasText(location, "location required");
Element errorPageElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "error-page[exception-type = '" + exceptionType + "']", document.getDocumentElement());
if (errorPageElement == null) {
errorPageElement = new XmlElementBuilder("error-page", document).addChild(new XmlElementBuilder("exception-type", document).setText(exceptionType).build()).build();
insertBetween(errorPageElement, "welcome-file-list[last()]", "the-end", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(errorPageElement, comment, document);
}
}
appendChildIfNotPresent(errorPageElement, new XmlElementBuilder("location", document).setText(location).build());
}
/**
* Add error code to web.xml document
*
* @param errorCode (required)
* @param location (required)
* @param document (required)
* @param comment (optional)
*/
public static void addErrorCode(final Integer errorCode, final String location, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.notNull(errorCode, "Error code required");
Assert.hasText(location, "Location required");
Element errorPageElement = XmlUtils.findFirstElement(WEB_APP_XPATH + "error-page[error-code = '" + errorCode.toString() + "']", document.getDocumentElement());
if (errorPageElement == null) {
errorPageElement = new XmlElementBuilder("error-page", document).addChild(new XmlElementBuilder("error-code", document).setText(errorCode.toString()).build()).build();
insertBetween(errorPageElement, "welcome-file-list[last()]", "the-end", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(errorPageElement, comment, document);
}
}
appendChildIfNotPresent(errorPageElement, new XmlElementBuilder("location", document).setText(location).build());
}
/**
* Add a security constraint to a web.xml document
*
* @param displayName (optional)
* @param webResourceCollections (required)
* @param roleNames (optional)
* @param transportGuarantee (optional)
* @param document (required)
* @param comment (optional)
* */
public static void addSecurityConstraint(final String displayName, final List<WebResourceCollection> webResourceCollections, final List<String> roleNames, final String transportGuarantee, final Document document, final String comment) {
Assert.notNull(document, "Web XML document required");
Assert.isTrue(!CollectionUtils.isEmpty(webResourceCollections), "A security-constraint element must contain at least one web-resource-collection");
Element securityConstraintElement = XmlUtils.findFirstElement("security-constraint", document.getDocumentElement());
if (securityConstraintElement == null) {
securityConstraintElement = document.createElement("security-constraint");
insertAfter(securityConstraintElement, "session-config[last()]", document);
if (StringUtils.hasText(comment)) {
addCommentBefore(securityConstraintElement, comment, document);
}
}
if (StringUtils.hasText(displayName)) {
appendChildIfNotPresent(securityConstraintElement, new XmlElementBuilder("display-name", document).setText(displayName).build());
}
for (final WebResourceCollection webResourceCollection : webResourceCollections) {
final XmlElementBuilder webResourceCollectionBuilder = new XmlElementBuilder("web-resource-collection", document);
Assert.hasText(webResourceCollection.getWebResourceName(), "web-resource-name is required");
webResourceCollectionBuilder.addChild(new XmlElementBuilder("web-resource-name", document).setText(webResourceCollection.getWebResourceName()).build());
if (StringUtils.hasText(webResourceCollection.getDescription())) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("description", document).setText(webResourceCollection.getWebResourceName()).build());
}
for (final String urlPattern : webResourceCollection.getUrlPatterns()) {
if (StringUtils.hasText(urlPattern)) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("url-pattern", document).setText(urlPattern).build());
}
}
for (final String httpMethod : webResourceCollection.getHttpMethods()) {
if (StringUtils.hasText(httpMethod)) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("http-method", document).setText(httpMethod).build());
}
}
appendChildIfNotPresent(securityConstraintElement, webResourceCollectionBuilder.build());
}
if (roleNames != null && roleNames.size() > 0) {
final XmlElementBuilder authConstraintBuilder = new XmlElementBuilder("auth-constraint", document);
for (final String roleName : roleNames) {
if (StringUtils.hasText(roleName)) {
authConstraintBuilder.addChild(new XmlElementBuilder("role-name", document).setText(roleName).build());
}
}
appendChildIfNotPresent(securityConstraintElement, authConstraintBuilder.build());
}
if (StringUtils.hasText(transportGuarantee)) {
final XmlElementBuilder userDataConstraintBuilder = new XmlElementBuilder("user-data-constraint", document);
userDataConstraintBuilder.addChild(new XmlElementBuilder("transport-guarantee", document).setText(transportGuarantee).build());
appendChildIfNotPresent(securityConstraintElement, userDataConstraintBuilder.build());
}
}
private static void insertBetween(final Element element, final String afterElementName, final String beforeElementName, final Document document) {
final Element beforeElement = XmlUtils.findFirstElement(WEB_APP_XPATH + beforeElementName, document.getDocumentElement());
if (beforeElement != null) {
document.getDocumentElement().insertBefore(element, beforeElement);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
final Element afterElement = XmlUtils.findFirstElement(WEB_APP_XPATH + afterElementName, document.getDocumentElement());
if (afterElement != null && afterElement.getNextSibling() != null && afterElement.getNextSibling() instanceof Element) {
document.getDocumentElement().insertBefore(element, afterElement.getNextSibling());
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
private static void insertBefore(final Element element, final String beforeElementName, final Document document) {
final Element beforeElement = XmlUtils.findFirstElement(WEB_APP_XPATH + beforeElementName, document.getDocumentElement());
if (beforeElement != null) {
document.getDocumentElement().insertBefore(element, beforeElement);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
private static void insertAfter(final Element element, final String afterElementName, final Document document) {
final Element afterElement = XmlUtils.findFirstElement(WEB_APP_XPATH + afterElementName, document.getDocumentElement());
if (afterElement != null && afterElement.getNextSibling() != null && afterElement.getNextSibling() instanceof Element) {
document.getDocumentElement().insertBefore(element, afterElement.getNextSibling());
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
/**
* Adds the given child to the given parent if it's not already there
*
* @param parent the parent to which to add a child (required)
* @param child the child to add if not present (required)
*/
private static void appendChildIfNotPresent(final Node parent, final Element child) {
final NodeList existingChildren = parent.getChildNodes();
for (int i = 0; i < existingChildren.getLength(); i++) {
final Node existingChild = existingChildren.item(i);
if (existingChild instanceof Element) {
// Attempt matching of possibly nested structures by using of 'getTextContent' as 'isEqualNode' does not match due to line returns, etc
// Note, this does not work if child nodes are appearing in a different order than expected
if (existingChild.getNodeName().equals(child.getNodeName()) && existingChild.getTextContent().replaceAll(WHITESPACE, "").trim().equals(child.getTextContent().replaceAll(WHITESPACE, ""))) {
// If we found a match, there is no need to append the child element
return;
}
}
}
parent.appendChild(child);
}
private static void addLineBreakBefore(final Element element, final Document document) {
document.getDocumentElement().insertBefore(document.createTextNode("\n "), element);
}
private static void addCommentBefore(final Element element, final String comment, final Document document) {
if (null == XmlUtils.findNode("//comment()[.=' " + comment + " ']", document.getDocumentElement())) {
document.getDocumentElement().insertBefore(document.createComment(" " + comment + " "), element);
addLineBreakBefore(element, document);
}
}
/**
* Value object that holds init-param style information
*
* @author Stefan Schmidt
* @since 1.1
*/
public static class WebXmlParam extends Pair<String, String> {
/**
* Constructor
*
* @param name
* @param value
*/
public WebXmlParam(final String name, final String value) {
super(name, value);
}
/**
* Returns the name of this parameter
*
* @return
*/
public String getName() {
return getKey();
}
}
/**
* Enum to define filter position
*
* @author Stefan Schmidt
* @since 1.1
*
*/
public static enum FilterPosition {
FIRST, LAST, BEFORE, AFTER, BETWEEN;
}
/**
* Enum to define dispatcher
*
* @author Stefan Schmidt
* @since 1.1.1
*
*/
public static enum Dispatcher {
FORWARD, REQUEST, INCLUDE, ERROR;
}
/**
* Convenience class for passing a web-resource-collection element's details
*
* @since 1.1.1
*/
public static class WebResourceCollection {
private final String webResourceName;
private final String description;
private final List<String> urlPatterns;
private final List<String> httpMethods;
public WebResourceCollection(final String webResourceName, final String description, final List<String> urlPatterns, final List<String> httpMethods) {
this.webResourceName = webResourceName;
this.description = description;
this.urlPatterns = urlPatterns;
this.httpMethods = httpMethods;
}
public String getWebResourceName() {
return webResourceName;
}
public List<String> getUrlPatterns() {
return urlPatterns;
}
public List<String> getHttpMethods() {
return httpMethods;
}
public String getDescription() {
return description;
}
}
/**
* Constructor is private to prevent instantiation
*/
private WebXmlUtils() {
}
}

View File

@@ -0,0 +1,77 @@
package org.springframework.roo.support.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Very simple convenience Builder for XML {@code Element}s
*
* @author Stefan Schmidt
* @since 1.0
*/
public class XmlElementBuilder {
// Fields
private final Element element;
/**
* Create a new Element instance.
*
* @param name The name of the element (required, not empty)
* @param document The parent document (required)
*/
public XmlElementBuilder(final String name, final Document document) {
Assert.hasText(name, "Element name required");
Assert.notNull(document, "Owner document required");
element = document.createElement(name);
}
/**
* Add an attribute to the current element.
*
* @param qName The attribute name (required, not empty)
* @param value The value of the attribute (required)
* @return the current XmlElementBuilder
*/
public XmlElementBuilder addAttribute(final String qName, final String value) {
Assert.hasText(qName, "Attribute qName required");
Assert.notNull(value, "Attribute value required");
element.setAttribute(qName, value);
return this;
}
/**
* Add a child node to the current element.
*
* @param node The new node (required)
* @return The builder for the current element
*/
public XmlElementBuilder addChild(final Node node) {
Assert.notNull(node, "Node required");
this.element.appendChild(node);
return this;
}
/**
* Add text contents to the current element. This will overwrite
* any previous text content.
*
* @param text The text content (required, not empty)
* @return The builder for the current element
*/
public XmlElementBuilder setText(final String text) {
Assert.hasText(text, "Text content required");
element.setTextContent(text);
return this;
}
/**
* Get the element instance.
*
* @return The element.
*/
public Element build() {
return element;
}
}

View File

@@ -0,0 +1,211 @@
package org.springframework.roo.support.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Utilities related to round-tripping XML documents
*
* @author Stefan Schmidt
* @since 1.1
*/
public final class XmlRoundTripUtils {
private static MessageDigest digest;
static {
try {
digest = MessageDigest.getInstance("sha-1");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Could not create hash key for identifier");
}
}
/**
* Create a base 64 encoded SHA1 hash key for a given XML element. The key is based on the
* element name, the attribute names and their values. Child elements are ignored.
* Attributes named 'z' are not concluded since they contain the hash key itself.
*
* @param element The element to create the base 64 encoded hash key for
* @return the unique key
*/
public static String calculateUniqueKeyFor(final Element element) {
StringBuilder sb = new StringBuilder();
sb.append(element.getTagName());
NamedNodeMap attributes = element.getAttributes();
SortedMap<String, String> attrKVStore = Collections.synchronizedSortedMap(new TreeMap<String, String>());
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Node attr = attributes.item(i);
if (!"z".equals(attr.getNodeName()) && !attr.getNodeName().startsWith("_")) {
attrKVStore.put(attr.getNodeName(), attr.getNodeValue());
}
}
for (Entry<String, String> entry: attrKVStore.entrySet()) {
sb.append(entry.getKey()).append(entry.getValue());
}
return base64(sha1(sb.toString().getBytes()));
}
/**
* This method will compare the original document with the proposed document and return
* true if adjustments to the original document were necessary. Adjustments are only made if new elements or
* attributes are proposed. Changes to the order of attributes or elements in the
* original document will not result in an adjustment.
*
* @param original document as read from the file system
* @param proposed document as determined by the JspViewManager
* @return true if the document was adjusted, otherwise false
*/
public static boolean compareDocuments(final Document original, final Document proposed) {
boolean originalDocumentAdjusted = checkNamespaces(original, proposed);
originalDocumentAdjusted |= addOrUpdateElements(original.getDocumentElement(), proposed.getDocumentElement(), originalDocumentAdjusted);
originalDocumentAdjusted |= removeElements(original.getDocumentElement(), proposed.getDocumentElement(), originalDocumentAdjusted);
return originalDocumentAdjusted;
}
/**
* Compare necessary namespace declarations between original and proposed document, if
* namespaces in the original are missing compared to the proposed, we add them to the
* original.
*
* @param original document as read from the file system
* @param proposed document as determined by the JspViewManager
* @return true if the document was adjusted, otherwise false
*/
private static boolean checkNamespaces(final Document original, final Document proposed) {
boolean originalDocumentChanged = false;
NamedNodeMap nsNodes = proposed.getDocumentElement().getAttributes();
for (int i = 0; i < nsNodes.getLength(); i++) {
if (0 == original.getDocumentElement().getAttribute(nsNodes.item(i).getNodeName()).length()) {
original.getDocumentElement().setAttribute(nsNodes.item(i).getNodeName(), nsNodes.item(i).getNodeValue());
originalDocumentChanged = true;
}
}
return originalDocumentChanged;
}
private static boolean addOrUpdateElements(final Element original, final Element proposed, boolean originalDocumentChanged) {
NodeList proposedChildren = proposed.getChildNodes();
for (int i = 0, n = proposedChildren.getLength(); i < n; i++) { // Check proposed elements and compare to originals to find out if we need to add or replace elements
Node node = proposedChildren.item(i);
if (node != null && node.getNodeType() == Node.ELEMENT_NODE) {
Element proposedElement = (Element) node;
String proposedId = proposedElement.getAttribute("id");
if (proposedId.length() != 0) { // Only proposed elements with an id will be considered
Element originalElement = XmlUtils.findFirstElement("//*[@id='" + proposedId + "']", original);
if (null == originalElement) { // Insert proposed element given the original document has no element with a matching id
Element placeHolder = DomUtils.findFirstElementByName("util:placeholder", original);
if (placeHolder != null) { // Insert right before place holder if we can find it
placeHolder.getParentNode().insertBefore(original.getOwnerDocument().importNode(proposedElement, false), placeHolder);
} else { // Find the best place to insert the element
if (proposed.getAttribute("id").length() != 0) { // Try to find the id of the proposed element's parent id in the original document
Element originalParent = XmlUtils.findFirstElement("//*[@id='" + proposed.getAttribute("id") + "']", original);
if (originalParent != null) { // Found parent with the same id, so we can just add it as new child
originalParent.appendChild(original.getOwnerDocument().importNode(proposedElement, false));
} else { // No parent found so we add it as a child of the root element (last resort)
original.appendChild(original.getOwnerDocument().importNode(proposedElement, false));
}
} else { // No parent found so we add it as a child of the root element (last resort)
original.appendChild(original.getOwnerDocument().importNode(proposedElement, false));
}
}
originalDocumentChanged = true;
} else { // We found an element in the original document with a matching id
String originalElementHashCode = originalElement.getAttribute("z");
if (originalElementHashCode.length() > 0) { // Only act if a hash code exists
if ("?".equals(originalElementHashCode) || originalElementHashCode.equals(calculateUniqueKeyFor(originalElement))) { // Only act if hash codes match (no user changes in the element) or the user requests for the hash code to be regenerated
if (!equalElements(originalElement, proposedElement)) { // Check if the elements have equal contents
originalElement.getParentNode().replaceChild(original.getOwnerDocument().importNode(proposedElement, false), originalElement); //replace the original with the proposed element
originalDocumentChanged = true;
}
if ("?".equals(originalElementHashCode)) { // Replace z if the user sets its value to '?' as an indication that roo should take over the management of this element again
originalElement.setAttribute("z", calculateUniqueKeyFor(proposedElement));
originalDocumentChanged = true;
}
} else { // If hash codes don't match we will mark the element as z="user-managed"
if (!originalElementHashCode.equals("user-managed")) {
originalElement.setAttribute("z", "user-managed"); // Mark the element as 'user-managed' if the hash codes don't match any more
originalDocumentChanged = true;
}
}
}
}
}
originalDocumentChanged = addOrUpdateElements(original, proposedElement, originalDocumentChanged); // Walk through the document tree recursively
}
}
return originalDocumentChanged;
}
private static boolean removeElements(final Element original, final Element proposed, boolean originalDocumentChanged) {
NodeList originalChildren = original.getChildNodes();
for (int i = 0, n = originalChildren.getLength(); i < n; i++) { // Check original elements and compare to proposed to find out if we need to remove elements
Node node = originalChildren.item(i);
if (node != null && node.getNodeType() == Node.ELEMENT_NODE) {
Element originalElement = (Element) node;
String originalId = originalElement.getAttribute("id");
if (originalId.length() != 0) { // Only proposed elements with an id will be considered
Element proposedElement = XmlUtils.findFirstElement("//*[@id='" + originalId + "']", proposed);
if (null == proposedElement && (originalElement.getAttribute("z").equals(calculateUniqueKeyFor(originalElement)) || originalElement.getAttribute("z").equals("?"))) { // Remove original element given the proposed document has no element with a matching id
originalElement.getParentNode().removeChild(originalElement);
originalDocumentChanged = true;
}
}
originalDocumentChanged = removeElements(originalElement, proposed, originalDocumentChanged); // Walk through the document tree recursively
}
}
return originalDocumentChanged;
}
private static boolean equalElements(final Element a, final Element b) {
if (!a.getTagName().equals(b.getTagName())) {
return false;
}
NamedNodeMap attributes = a.getAttributes();
int customAttributeCounter = 0;
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Node node = attributes.item(i);
if (node != null && !node.getNodeName().startsWith("_")) {
if (!node.getNodeName().equals("z") && (b.getAttribute(node.getNodeName()).length() == 0 || !b.getAttribute(node.getNodeName()).equals(node.getNodeValue()))) {
return false;
}
} else {
customAttributeCounter++;
}
}
if (a.getAttributes().getLength() - customAttributeCounter != b.getAttributes().getLength()) {
return false;
}
return true;
}
/**
* Creates a sha-1 hash value for the given data byte array.
*
* @param data to hash
* @return byte[] hash of the input data
*/
private static byte[] sha1(final byte[] data) {
Assert.notNull(digest, "Could not create hash key for identifier");
return digest.digest(data);
}
private static String base64(final byte[] data) {
return Base64.encodeBytes(data);
}
/**
* Constructor is private to prevent instantiation
*/
private XmlRoundTripUtils() {}
}

View File

@@ -0,0 +1,558 @@
package org.springframework.roo.support.util;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSException;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.SAXException;
/**
* Utilities related to XML usage.
*
* @author Stefan Schmidt
* @author Ben Alex
* @author Alan Stewart
* @author Andrew Swan
* @since 1.0
*/
public final class XmlUtils {
// Constants
private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
private static final Map<String, XPathExpression> compiledExpressionCache = new HashMap<String, XPathExpression>();
private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
private static final XPath xpath = XPathFactory.newInstance().newXPath();
/**
* Returns the given XML as the root {@link Element} of a new {@link Document}
*
* @param xml the XML to convert; can be blank
* @return <code>null</code> if the given XML is blank
* @since 1.2.0
*/
public static Element stringToElement(final String xml) {
if (StringUtils.isBlank(xml)) {
return null;
}
try {
return factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes())).getDocumentElement();
} catch (final IOException e) {
throw new IllegalStateException(e);
} catch (final ParserConfigurationException e) {
throw new IllegalStateException(e);
} catch (final SAXException e) {
throw new IllegalStateException(e);
}
}
/**
* Creates an {@link Element} containing the given text
*
* @param document the document to contain the new element
* @param tagName the element's tag name (required)
* @param text the text to set; can be <code>null</code> for none
* @return a non-<code>null</code> element
* @since 1.2.0
*/
public static Element createTextElement(final Document document, final String tagName, final String text) {
final Element element = document.createElement(tagName);
element.setTextContent(text);
return element;
}
/**
* Read an XML document from the supplied input stream and return a document.
*
* @param inputStream the input stream to read from (required). The stream is closed upon completion.
* @return a document.
* @throws IllegalStateException if the stream could not be read
*/
public static Document readXml(InputStream inputStream) {
Assert.notNull(inputStream, "InputStream required");
try {
if (!(inputStream instanceof BufferedInputStream)) {
inputStream = new BufferedInputStream(inputStream);
}
return getDocumentBuilder().parse(inputStream);
} catch (final Exception e) {
throw new IllegalStateException("Could not open input stream", e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
/**
* Write an XML document to the OutputStream provided. This will use the pre-configured Roo provided Transformer.
*
* @param outputStream the output stream to write to. The stream is closed upon completion.
* @param document the document to write.
*/
public static void writeXml(final OutputStream outputStream, final Document document) {
writeXml(createIndentingTransformer(), outputStream, document);
}
/**
* Write an XML document to the OutputStream provided. This will use the provided Transformer.
*
* @param transformer the transformer (can be obtained from XmlUtils.createIndentingTransformer())
* @param outputStream the output stream to write to. The stream is closed upon completion.
* @param document the document to write.
*/
public static void writeXml(final Transformer transformer, OutputStream outputStream, final Document document) {
Assert.notNull(transformer, "Transformer required");
Assert.notNull(outputStream, "OutputStream required");
Assert.notNull(document, "Document required");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
try {
if (!(outputStream instanceof BufferedOutputStream)) {
outputStream = new BufferedOutputStream(outputStream);
}
final StreamResult streamResult = createUnixStreamResultForEntry(outputStream);
transformer.transform(new DOMSource(document), streamResult);
} catch (final Exception e) {
throw new IllegalStateException(e);
} finally {
IOUtils.closeQuietly(outputStream);
}
}
/**
* Write an XML document to the OutputStream provided. This method will detect if the JDK supports the
* DOM Level 3 "format-pretty-print" configuration and make use of it. If not found it will fall back to
* using formatting offered by TrAX.
*
* @param outputStream the output stream to write to. The stream is closed upon completion.
* @param document the document to write.
*/
public static void writeFormattedXml(final OutputStream outputStream, final Document document) {
// Note that the "format-pretty-print" DOM configuration parameter can only be set in JDK 1.6+.
final DOMImplementation domImplementation = document.getImplementation();
if (domImplementation.hasFeature("LS", "3.0") && domImplementation.hasFeature("Core", "2.0")) {
DOMImplementationLS domImplementationLS = null;
try {
domImplementationLS = (DOMImplementationLS) domImplementation.getFeature("LS", "3.0");
} catch (final NoSuchMethodError nsme) {
// Fall back to default LS
DOMImplementationRegistry registry = null;
try {
registry = DOMImplementationRegistry.newInstance();
} catch (final Exception e) {
// DOMImplementationRegistry not available. Falling back to TrAX.
writeXml(outputStream, document);
return;
}
if (registry != null) {
domImplementationLS = (DOMImplementationLS) registry.getDOMImplementation("LS");
} else {
// DOMImplementationRegistry not available. Falling back to TrAX.
writeXml(outputStream, document);
}
}
if (domImplementationLS != null) {
final LSSerializer lsSerializer = domImplementationLS.createLSSerializer();
final DOMConfiguration domConfiguration = lsSerializer.getDomConfig();
if (domConfiguration.canSetParameter("format-pretty-print", Boolean.TRUE)) {
lsSerializer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
final LSOutput lsOutput = domImplementationLS.createLSOutput();
lsOutput.setEncoding("UTF-8");
lsOutput.setByteStream(outputStream);
try {
lsSerializer.write(document, lsOutput);
} catch (final LSException lse) {
throw new IllegalStateException(lse);
} finally {
IOUtils.closeQuietly(outputStream);
}
} else {
// DOMConfiguration 'format-pretty-print' parameter not available. Falling back to TrAX.
writeXml(outputStream, document);
}
} else {
// DOMImplementationLS not available. Falling back to TrAX.
writeXml(outputStream, document);
}
} else {
// DOM 3.0 LS and/or DOM 2.0 Core not supported. Falling back to TrAX.
writeXml(outputStream, document);
}
}
/**
* Compares two DOM {@link Node nodes} by comparing the representations of the nodes as XML strings
*
* @param node1 the first node
* @param node2 the second node
* @return true if the XML representation node1 is the same as the XML representation of node2, otherwise false
*/
public static boolean compareNodes(Node node1, Node node2) {
Assert.notNull(node1, "First node required");
Assert.notNull(node2, "Second node required");
// The documents need to be cloned as normalization has side-effects
node1 = node1.cloneNode(true);
node2 = node2.cloneNode(true);
// The documents need to be normalized before comparison takes place to remove any formatting that interfere with comparison
if (node1 instanceof Document && node2 instanceof Document) {
((Document) node1).normalizeDocument();
((Document) node2).normalizeDocument();
} else {
node1.normalize();
node2.normalize();
}
return nodeToString(node1).equals(nodeToString(node2));
}
/**
* Converts a {@link Node node} to an XML string
*
* @param node the first element
* @return the XML String representation of the node, never null
*/
public static String nodeToString(final Node node) {
try {
final StringWriter writer = new StringWriter();
createIndentingTransformer().transform(new DOMSource(node), new StreamResult(writer));
return writer.toString();
} catch (final TransformerException e) {
throw new IllegalStateException(e);
}
}
/**
* Creates a {@link StreamResult} by wrapping the given outputStream in an
* {@link OutputStreamWriter} that transforms Windows line endings (\r\n)
* into Unix line endings (\n) on Windows for consistency with Roo's templates.
*
* @param outputStream
* @return StreamResult
* @throws UnsupportedEncodingException
*/
private static StreamResult createUnixStreamResultForEntry(final OutputStream outputStream) throws UnsupportedEncodingException {
final Writer writer;
if (StringUtils.LINE_SEPARATOR.equals("\r\n")) {
writer = new OutputStreamWriter(outputStream, "ISO-8859-1") {
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
for (int i = off; i < off + len; i++) {
if (cbuf[i] != '\r' || (i < cbuf.length - 1 && cbuf[i + 1] != '\n')) {
super.write(cbuf[i]);
}
}
}
@Override
public void write(final int c) throws IOException {
if (c != '\r') super.write(c);
}
@Override
public void write(final String str, final int off, final int len) throws IOException {
final String orig = str.substring(off, off + len);
final String filtered = orig.replace("\r\n", "\n");
final int lengthDiff = orig.length() - filtered.length();
if (filtered.endsWith("\r")) {
super.write(filtered.substring(0, filtered.length() - 1), 0, len - lengthDiff - 1);
} else {
super.write(filtered, 0, len - lengthDiff);
}
}
};
} else {
writer = new OutputStreamWriter(outputStream, "ISO-8859-1");
}
return new StreamResult(writer);
}
/**
* Searches the given parent element for a child element matching the given
* XPath expression.
*
* Please note that the XPath parser used is NOT namespace aware. So if you
* want to find an element <code>&lt;beans&gt;&lt;sec:http&gt;</code>, you
* need to use the following XPath expression '/beans/http'.
*
* @param xPathExpression the xPathExpression (required)
* @param parent the parent DOM element (required)
* @return the Element if discovered (null if no such {@link Element} found)
*/
public static Element findFirstElement(final String xPathExpression, final Node parent) {
final Node node = findNode(xPathExpression, parent);
if (node instanceof Element) {
return (Element) node;
}
return null;
}
/**
* Checks in under a given root element whether it can find a child node
* which matches the XPath expression supplied. Returns {@link Node} if
* exists.
*
* Please note that the XPath parser used is NOT namespace aware. So if you
* want to find a element <code>&lt;beans&gt;&lt;sec:http&gt;</code>, you
* need to use the XPath expression '<code>/beans/http</code>'.
*
* @param xPathExpression the XPath expression (required)
* @param root the parent DOM element (required)
* @return the Node if discovered (null if not found)
*/
public static Node findNode(final String xPathExpression, final Node root) {
Assert.hasText(xPathExpression, "XPath expression required");
Assert.notNull(root, "Root element required");
Node node = null;
try {
XPathExpression expr = compiledExpressionCache.get(xPathExpression);
if (expr == null) {
expr = xpath.compile(xPathExpression);
compiledExpressionCache.put(xPathExpression, expr);
}
node = (Node) expr.evaluate(root, XPathConstants.NODE);
} catch (final XPathExpressionException e) {
throw new IllegalArgumentException("Unable evaluate XPath expression '" + xPathExpression + "'", e);
}
return node;
}
/**
* Checks in under a given root element whether it can find a child element
* which matches the XPath expression supplied. The {@link Element} must
* exist. Returns {@link Element} if exists.
*
* Please note that the XPath parser used is NOT namespace aware. So if you
* want to find a element <beans><sec:http> you need to use the following
* XPath expression '/beans/http'.
*
* @param xPathExpression the XPath expression (required)
* @param root the parent DOM element (required)
* @return the Element if discovered (never null; an exception is thrown if cannot be found)
*/
public static Element findRequiredElement(final String xPathExpression, final Element root) {
Assert.hasText(xPathExpression, "XPath expression required");
Assert.notNull(root, "Root element required");
final Element element = findFirstElement(xPathExpression, root);
Assert.notNull(element, "Unable to obtain required element '" + xPathExpression + "' from element '" + root + "'");
return element;
}
/**
* Checks in under a given root element whether it can find a child elements
* which match the XPath expression supplied. Returns a {@link List} of
* {@link Element} if they exist.
*
* Please note that the XPath parser used is NOT namespace aware. So if you
* want to find a element <beans><sec:http> you need to use the following
* XPath expression '/beans/http'.
*
* @param xPathExpression the xPathExpression
* @param root the parent DOM element
* @return a {@link List} of type {@link Element} if discovered, otherwise an empty list (never null)
*/
public static List<Element> findElements(final String xPathExpression, final Element root) {
final List<Element> elements = new ArrayList<Element>();
NodeList nodes = null;
try {
XPathExpression expr = compiledExpressionCache.get(xPathExpression);
if (expr == null) {
expr = xpath.compile(xPathExpression);
compiledExpressionCache.put(xPathExpression, expr);
}
nodes = (NodeList) expr.evaluate(root, XPathConstants.NODESET);
} catch (final XPathExpressionException e) {
throw new IllegalArgumentException("Unable evaluate xpath expression", e);
}
for (int i = 0, n = nodes.getLength(); i < n; i++) {
elements.add((Element) nodes.item(i));
}
return elements;
}
/**
* Checks for a given element whether it can find an attribute which matches the
* XPath expression supplied. Returns {@link Node} if exists.
*
* @param xPathExpression the xPathExpression (required)
* @param element (required)
* @return the Node if discovered (null if not found)
*/
public static Node findFirstAttribute(final String xPathExpression, final Element element) {
Node attr = null;
try {
XPathExpression expr = compiledExpressionCache.get(xPathExpression);
if (expr == null) {
expr = xpath.compile(xPathExpression);
compiledExpressionCache.put(xPathExpression, expr);
}
attr = (Node) expr.evaluate(element, XPathConstants.NODE);
} catch (final XPathExpressionException e) {
throw new IllegalArgumentException("Unable evaluate xpath expression", e);
}
return attr;
}
/**
* @return a transformer that indents entries by 4 characters (never null)
*/
public static Transformer createIndentingTransformer() {
Transformer transformer;
try {
transformerFactory.setAttribute("indent-number", 4);
transformer = transformerFactory.newTransformer();
} catch (final Exception e) {
throw new IllegalStateException(e);
}
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
return transformer;
}
/**
* @return a new document builder (never null)
*/
public static DocumentBuilder getDocumentBuilder() {
// factory.setNamespaceAware(true);
try {
return factory.newDocumentBuilder();
} catch (final ParserConfigurationException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns the root element of the given XML file.
*
* @param clazz the class from whose package to open the file (required)
* @param xmlFilePath the path of the XML file relative to the given class'
* package (required)
* @return a non-<code>null</code> element
* @see Document#getDocumentElement()
*/
public static Element getRootElement(final Class<?> clazz, final String xmlFilePath) {
final InputStream inputStream = FileUtils.getInputStream(clazz, xmlFilePath);
Assert.notNull(inputStream, "Could not open the file '" + xmlFilePath + "'");
return readXml(inputStream).getDocumentElement();
}
/**
* Returns the root element of an addon's configuration file.
*
* @param clazz which owns the configuration
* @return the configuration root element
*/
public static Element getConfiguration(final Class<?> clazz) {
return getRootElement(clazz, "configuration.xml");
}
/**
* Converts a XHTML compliant id (used in jspx) to a CSS3 selector spec compliant id. In that
* it will replace all '.,:,-' to '_'
*
* @param proposed Id
* @return cleaned up Id
*/
public static String convertId(final String proposed) {
return proposed.replaceAll("[:\\.-]", "_");
}
/**
* Checks the presented element for illegal characters that could cause malformed XML.
*
* @param element the content of the XML element
* @throws IllegalArgumentException if the element is null, has no text or contains illegal characters
*/
public static void assertElementLegal(final String element) {
if (StringUtils.isBlank(element)) {
throw new IllegalArgumentException("Element required");
}
// Note regular expression for legal characters found to be x5 slower in profiling than this approach
final char[] value = element.toCharArray();
for (int i = 0; i < value.length; i++) {
final char c = value[i];
if (' ' == c || '*' == c || '>' == c || '<' == c || '!' == c || '@' == c || '%' == c || '^' == c ||
'?' == c || '(' == c || ')' == c || '~' == c || '`' == c || '{' == c || '}' == c || '[' == c || ']' == c ||
'|' == c || '\\' == c || '\'' == c || '+' == c) {
throw new IllegalArgumentException("Illegal name '" + element + "' (illegal character)");
}
}
}
public static String getTextContent(final String path, final Element parentElement) {
return getTextContent(path, parentElement, null);
}
public static String getTextContent(final String path, final Element parentElement, final String valueIfNull) {
final Element element = XmlUtils.findFirstElement(path, parentElement);
if (element != null) {
return element.getTextContent();
}
return valueIfNull;
}
/**
* Checks in under a given root element whether it can find a child element
* which matches the name supplied. Returns {@link Element} if exists.
*
* @param name the Element name (required)
* @param root the parent DOM element (required)
* @return the Element if discovered
* @deprecated use {@link DomUtils#findFirstElementByName(String, Element)} instead
*/
@Deprecated
public static Element findFirstElementByName(final String name, final Element root) {
return DomUtils.findFirstElementByName(name, root);
}
/**
* Removes empty text nodes from the specified node
*
* @param node the element where empty text nodes will be removed
* @deprecated use {@link DomUtils#removeTextNodes(Node)} instead
*/
@Deprecated
public static void removeTextNodes(final Node node) {
DomUtils.removeTextNodes(node);
}
/**
* Constructor is private to prevent instantiation
*/
private XmlUtils() {}
}

View File

@@ -6,21 +6,31 @@ package org.springframework.shell;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
//import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.roo.shell.AbstractShell;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.ExitShellRequest;
import org.springframework.roo.shell.Shell;
import org.springframework.roo.shell.converters.StringConverter;
import org.springframework.roo.shell.event.ShellStatus;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.Assert;
import org.springframework.shell.plugin.PluginConfigurationReader;
import org.springframework.shell.plugin.PluginInfo;
import org.springframework.util.ClassUtils;
import org.springframework.util.StopWatch;
//import ch.qos.logback.classic.LoggerContext;
@@ -66,7 +76,7 @@ public class Bootstrap {
Assert.hasText(applicationContextLocation, "Application context location required");
ctx = new ClassPathXmlApplicationContext(applicationContextLocation);
createApplicationContext(applicationContextLocation);
Map<String, JLineShellComponent> shells = ctx.getBeansOfType(JLineShellComponent.class);
@@ -113,6 +123,54 @@ public class Bootstrap {
}
private void createApplicationContext(String applicationContextLocation) {
//ctx = new ClassPathXmlApplicationContext(applicationContextLocation);
AnnotationConfigApplicationContext annctx = new AnnotationConfigApplicationContext();
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.StringConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.AvailableCommandsConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.BigDecimalConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.BigIntegerConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.BooleanConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.CharacterConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.DateConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.DoubleConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.EnumConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.FloatConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.IntegerConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.LocaleConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.LongConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.ShortConverter.class);
createAndRegisterBeanDefinition(annctx, org.springframework.roo.shell.converters.StaticFieldConverterImpl.class);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
PluginConfigurationReader configReader = new PluginConfigurationReader(resourcePatternResolver);
PluginInfo[] pluginInfos = configReader.readPluginInfos("classpath*:/META-INF/spring/spring-shell-plugin.xml");
for (int i = 0; i < pluginInfos.length; i++) {
List<String> configClassNames = pluginInfos[i].getConfigClassNames();
for (String configClassName : configClassNames) {
try {
annctx.register(ClassUtils.forName(configClassName, ClassUtils.getDefaultClassLoader()));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (LinkageError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//annctx.scan("org.springframework.shell");
annctx.refresh();
ctx = annctx;
}
protected void createAndRegisterBeanDefinition(AnnotationConfigApplicationContext annctx, Class clazz) {
RootBeanDefinition rbd = new RootBeanDefinition();
rbd.setBeanClass(clazz);
annctx.registerBeanDefinition(clazz.getSimpleName(), rbd);
}
protected ExitShellRequest run(String[] executeThenQuit) {
ExitShellRequest exitShellRequest;

View File

@@ -0,0 +1,12 @@
package org.springframework.shell.plugin;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"org.springframework.shell.commands", "org.springframework.shell.converters"})
public class HelloWorldPlugin {
}

View File

@@ -0,0 +1,129 @@
package org.springframework.shell.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.util.xml.SimpleSaxErrorHandler;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
public class PluginConfigurationReader {
private static final String CONFIGURATION = "configuration";
private static final String CONFIGURATION_CLASS_NAME = "class";
private final Log logger = LogFactory.getLog(getClass());
private final ResourcePatternResolver resourcePatternResolver;
public PluginConfigurationReader(ResourcePatternResolver resourcePatternResolver) {
Assert.notNull(resourcePatternResolver, "ResourceLoader must not be null");
this.resourcePatternResolver = resourcePatternResolver;
}
public PluginInfo[] readPluginInfos(String... pluginInfoXmlLocations) {
ErrorHandler handler = new SimpleSaxErrorHandler(logger);
List<PluginInfo> infos = new LinkedList<PluginInfo>();
String resourceLocation = null;
try {
for (String location : pluginInfoXmlLocations) {
Resource[] resources = this.resourcePatternResolver.getResources(location);
for (Resource resource : resources) {
resourceLocation = resource.toString();
InputStream stream = resource.getInputStream();
try {
Document document = buildDocument(handler, stream);
parseDocument(resource, document, infos);
}
finally {
stream.close();
}
}
}
}
catch (IOException ex) {
throw new IllegalArgumentException("Cannot parse persistence unit from " + resourceLocation, ex);
}
catch (SAXException ex) {
throw new IllegalArgumentException("Invalid XML in persistence unit from " + resourceLocation, ex);
}
catch (ParserConfigurationException ex) {
throw new IllegalArgumentException("Internal error parsing persistence unit from " + resourceLocation);
}
return infos.toArray(new PluginInfo[infos.size()]);
}
/**
* Parse the validated document and add entries to the given unit info list.
*/
protected List<PluginInfo> parseDocument(
Resource resource, Document document, List<PluginInfo> infos) throws IOException {
Element persistence = document.getDocumentElement();
List<Element> configurations = DomUtils.getChildElementsByTagName(persistence, CONFIGURATION);
for (Element configuration : configurations) {
PluginInfo info = parsePluginInfo(configuration);
infos.add(info);
}
return infos;
}
/**
* Parse the plugin DOM element.
*/
protected PluginInfo parsePluginInfo(Element configuration) throws IOException {
PluginInfo pluginInfo = new PluginInfo();
parseClass(configuration, pluginInfo);
return pluginInfo;
}
/**
* Parse the <code>class</code> XML elements.
*/
@SuppressWarnings("unchecked")
protected void parseClass(Element configuration, PluginInfo pluginInfo) {
List<Element> classes = DomUtils.getChildElementsByTagName(configuration, CONFIGURATION_CLASS_NAME);
for (Element element : classes) {
String value = DomUtils.getTextValue(element).trim();
if (StringUtils.hasText(value))
pluginInfo.addConfigurationClassName(value);
}
}
/**
* Validate the given stream and return a valid DOM document for parsing.
*/
protected Document buildDocument(ErrorHandler handler, InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder parser = dbf.newDocumentBuilder();
parser.setErrorHandler(handler);
return parser.parse(stream);
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.shell.plugin;
import java.util.LinkedList;
import java.util.List;
public class PluginInfo {
private List<String> configClassNames = new LinkedList<String>();
public void addConfigurationClassName(String configClassName) {
this.configClassNames.add(configClassName);
}
public List<String> getConfigClassNames() {
return this.configClassNames;
}
}